List解析

本文详细介绍了Java集合框架中的List接口及其实现类ArrayList、LinkedList和Vector。对比了它们的特点、适用场景以及内部实现机制,包括元素添加、删除、查询等操作的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

List
List是Collection的一个子接口,其特点是元素有序且可重复。其主要的实现类有ArrayList、LinkedList和Vector。
首先来看看ArrayList:在这里插入图片描述
从源码中可以看出,ArrayList实现了序列化接口,以及其底层是一个Object数组,并设置了一个默认容量大小为10。
接下来看下其构造器:

	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  	
  	public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  	
  	public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

·空参构造器,即List list = new ArrayList<>();时会底层会创建一个空数组;
·int型参数构造器,会对参数的取值进行非法性判断,若合理则会创建一个对应的初始值大小数组;
·集合类型构造器;

创建好了集合之后我们肯定要对其操作,先看看其添加元素的方法:

	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

	private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

·调用add方法添加一个元素,会先进行判断,调用ensureCapacityInternal(size+1);
·ensureCapacityInternal先调用calculateCapacity(eleData,minCapacity),然后调用ensureExplicitCapacity(int mincapacity)方法;
·calculateCapacity会得到数组的最小大小,若数组为空,则初始化为10;
·ensureExplicitCapacity会对需要的大小和数组大小进行比较,若超出了数组大小,则调用grow(minCapacity)方法
·grow方法会对数组容量进行扩展,容量扩展为原来的1.5倍,若仍比所需的容量小,则数组大小就为所需的容量大小,然后对此容量进行非法性判断,最后创建一个新数组,将原数组的内容复制到新的数组中。

添加完之后看看删除操作:

	public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

因为底层是数组,所以我们可以根据索引进行操作。
·首先调用rangeCheck方法,检测索引是否越界;
·然后获取数组在这个索引上的值;
·然后把这个索引后面的内容都往前覆盖一个,将最后多出来的一个空位设置为null,返回移除的元素。
因此,我们会发现删除集合中间的元素速率较低,因为会频繁进行规模性的元素移动。
继续查看api,我们会发现这样的代码:

	public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

ArrayList也能根据内容移除元素,那么问题来了:
在这里插入图片描述
上面List中存储的是整型元素,移除3是根据索引还是内容呢?运行下:
在这里插入图片描述
索引越界异常,说明是根据索引进行的remove操作,接下来用 Integer(3)试试:
在这里插入图片描述
操作成功,说明了在remove操作时不会进行自动装箱和拆箱操作。

接下来看看ArrayList的查询操作:

	public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    E elementData(int index) {
        return (E) elementData[index];
    }

·首先检查索引是否越界;
·通过索引到数组中取;
我们知道,数组通过索引取值是非常迅速的,因此ArrayList的查询某个元素是非常快的。

总结:当使用ArrayList时,默认创建一个空数组,第一次进行add操作时会进行扩容,初始容量为10,后来add时若容量不够再次扩容,扩容大小为原数组的1.5倍,若仍然不足,则创建一个minCapacity大小的数组,然后将旧数组的内容复制到新的数组中。ArrayList的增删操作性能较低,会频繁的移动元素;查询效率较高,通过索引的方式就能取得。

LinkedList
在这里插入图片描述
先看看字段,我们会发现其内部维护了一个Node的节点类,并设置了头尾节点。

在这里插入图片描述
查看Node会发现是一个双向链表。
还是先看看其add方法:

	public boolean add(E e) {
        linkLast(e);
        return true;
    }
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

实质是再链表尾部添加一个节点
因此,我们可以发现,LinkedList的操作实际和链表的操作没什么区别,添加和删除某个元素的操作时间复杂度为O(1),查找某个元素为O(n);

Vector
在这里插入图片描述
我们发现vector和ArrayList很像,且是jdk1.0就存在的类。区别在于Vector是线程安全的类,其方法都是synchronized修饰的,也因此效率会低于ArrayList。

看下Vector的构造方法:

	public Vector() {
        this(10);
    }
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

默认会创建一个初始容量为10的数组,这里就看出和ArrayList的差别,ArrayList是第一次add时才会创建初始容量10的数组,一种懒汉式、一种饿汉式。

接下来看看Vector的扩容机制:

	private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

capacityIncrement是在使用构造器时我们可以选择传入的参数,即自定义的扩容大小,若不进行传入,默认为0,每次扩容的新大小为原来数组的2倍。然后将旧数组的内容复制到新数组中。

然后就是size方法:

	public synchronized int size() {
        return elementCount;
    }

返回的是成员变量中的elementCount,ArrayList中就是size变量,记录了当前使用到的数组索引,而非数组的大小。

总结:当我们需要频繁的查找操作时,使用ArrayList更为合适,若需要线程安全可以用Vector或者Collections工具类的synchronizedList方法;当要频繁的在集合中插入或删除操作时使用LinkedList更为合适。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值