List集合的实现类总结

 一、集合类关系图

上述类图中,实线边框的是类,而点线边框的是接口。

 

二、ArrayList

继承关系、实现接口

//继承关系
java.lang.Object 
    java.util.AbstractCollection<E> 
        java.util.AbstractList<E> 
            java.util.ArrayList<E> 

//实现接口
List<E>, Iterable<E>, Collection<E>, Serializable, Cloneable, RandomAccess 

底层依赖

    /**
     * 存储ArrayList元素的数组缓冲区。
     * ArrayList的容量是此数组缓冲区的长度。
     */
    private transient Object[] elementData;

 ArrayList底层依赖数组。从源码中可以看出,它封装了一个Object[]类型的数组。而数组的优点就是方便查询

构造方法

    public ArrayList() {
        this(10);
    }

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

    public ArrayList(Collection<? extends E> c) {
        ......
    }

第一个无参构造:调用第二个构造方法,所传参数就是默认初始化集合容量,大小为10

第二个带参构造:自定义集合容量;

第三个带参构造:这里不进行说明。

添加方法

    //将指定的元素追加到此集合的末尾    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    //将指定元素插入此集合的指定位置
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

这里只讲解两个常用添加方法。

第一个添加方法:在集合末尾添加。

  • 首先,进行扩容检测 ensureCapacityInternal(size + 1);
  • 若需要扩容,先进行扩容,再进行添加(扩容后面讲解)
  • 若不需要扩容,直接添加即可。

第二个添加方法:在集合指定位置添加。

  • 首先,检测添加位置是否在集合容量范围内,若不在,抛出IndexOutOfBoundsException;
  • 然后,跟上一个添加方法一样,进行扩容检测;
  • 调用System.arraycopy方法将数组内位置为 index 到 (size-1)的元素往后移动一位(删除集合中的元素也是调用此方法,由此可见,该集合对于元素的增删效率较低)。
  • 继而,将元素添加到index处。

查找方法

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

        return elementData(index);
    }

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

首先,检测index是否在集合范围内,若不在,抛出IndexOutOfBoundsException;

然后直接根据index查找数组中的对应的值,并返回。

扩容操作

通过上面的add方法可知,每次在添加元素之前都会进行扩容检测。下面根据源码来分析ArrayList集合具体是怎么进行扩容的。

扩容操作的第一步:

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
  • 在添加元素的时候,会调用ensureCapacityInternal方法来判断是否需要扩容;
  • 当minCapacity(也就是add方法中的(size+1))大于数组长度时,将调用grow方法真正的进行扩容操作;
  • 其中,源码中的modCount++表示记录集合修改次数。

扩容操作的第二步:

    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);
    }
  • 源码中的int newCapacity = oldCapacity + (oldCapacity >> 1)就是扩容的重点。其中(oldCapacity >> 1)表示右移一位,可以看作(oldCapacity/2)。即扩容1.5倍
  • 后面的if语句是对容量的一系列判断,ArrayList最大容量为Integer.MAX_VALUE
  • 最后,将旧数组复制元素到新数组完成扩容操作。

ArrayList总结

ArrayList底层依赖数组来实现,查询效率较高,增删效率较低

ArrayList中的元素有序、可重复、允许null值

ArrayList会自动进行扩容1.5倍。初始化时尽量指定初始容量,可避免频繁扩容,影响程序执行效率

线程不安全,适用于单线程环境。

 

三、Vector

继承关系、实现接口

//继承关系
java.lang.Object 
    java.util.AbstractCollection<E> 
        java.util.AbstractList<E> 
            java.util.Vector<E> 

//实现接口
List<E>, Collection<E>, Iterable<E>, Serializable, Cloneable, RandomAccess

底层依赖

    protected Object[] elementData;

Vector的底层实现与ArrayLIst一样,也是依赖数组。

构造方法

    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;
    }

    public Vector(Collection<? extends E> c) {
        ......
    }

第一个无参构造:调用第二个构造方法,继而调用第三个构造方法,所传参数就是默认初始化集合容量,大小为10

第二个带参构造:调用第三个构造方法,自定义集合容量;

第三个带参构造:自定义集合容量,并设置容量增长量,如果增量大于0,则在扩容后的容量为原容量+增量

第四个带参构造:这里不进行说明。

添加方法

    //将指定的元素追加到此Vector的末尾。
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

Vector只有在集合末尾添加单个元素的方法(当然,它有在指定位置添加一个集合的方法,这里不作讲述),与ArrayList的add()方法类似。

有一点区别就是Vector的扩容检测方法是ensureCapacityHelper(elementCount + 1)。

查找方法

    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

同ArrayList的get方法类似。 

扩容操作

Vector的扩容与ArrayList有一些不同,主要是多了一个成员变量capacityIncrement,下面重点讲与ArrayList不同的地方。

    protected int capacityIncrement;

扩容操作的第一步:同ArrayList类似

    private void ensureCapacityHelper(int minCapacity) {
        // 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 + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

从源码中第三行代码可以看出Vector具体的扩容方法:

  • 在对集合进行初始化的时候,若没有通过构造方法对capacityIncrement赋值(默认为0)则集合扩容后的容量为原来的2倍

否则,集合扩容后的容量为原容量+增量

Vector总结

Vector底层依赖数组来实现,查询效率较高,增删效率较低

Vector中的元素有序、可重复、允许null值,添加单个元素的话,只能添加到集合末尾

Vector会自动进行扩容。扩容后的容量由增量来决定,(2倍 or 原容量+增量)

大多数方法用关键字synchronized修饰,线程安全。

 

四、LinkedList

继承关系、实现接口

//继承关系
java.lang.Object 
    java.util.AbstractCollection<E> 
        java.util.AbstractList<E> 
            java.util.AbstractSequentialList<E> 
                java.util.LinkedList<E>

//接口实现
List<E>, Queue<E>, Deque<E>, Collection<E>, Iterable<E>, Serializable, Cloneable

底层依赖

transient Node<E> first;

transient Node<E> last;

 LinkedList底层依赖链表。从源码中可以看出,它封装了头结点、尾节点。而链表的优点就是方便增删节点

若面试官细问,可以回答LinkedList底层依赖双向循环链表。因为双向链表包含两个指针,pre指向前一个节点,next指向后一个节点。 由下面源码可知,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,形成一个“环”。 

     private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    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++;
    }

 构造方法

    public LinkedList() {
    }

    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

第一个无参构造:LinkedList集合容量默认为空,没有扩容的概念。

第二个带参构造:这里不进行说明。

添加方法

    //将指定的元素追加到此列表的末尾。
    public boolean add(E e) {
        linkLast(e);
        return true;
    }  

    //将指定元素插入此列表中的指定位置。
     public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }    

查找方法

    //返回此列表中指定位置的元素。
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

首先,进行异常检测,若有异常,抛出IndexOutOfBoundsException;

因为LinkedList无法随机访问,只能通过遍历的方式找到相应的节点;

从源码中可以看出,为了提高效率,当前位置首先和元素数量的中间位置开始判断,小于中间位置,从头节点开始遍历,大于中间位置从尾节点开始遍历。

LinkedList总结

LinkedList底层依赖双向循环链表实现,增删效率较高,查询效率较低

LinkedList中的元素有序、可重复、允许null值

线程不安全,适用于单线程环境。

 

五、ArrayList、Vector、LinkedList总结

1.底层数据结构

ArrayList、Vector底层依赖数组,查询效率较高,增删效率较低(因为Vector是线程安全的,整体效率比ArrayList低)

LinkedList底层依赖双向循环链表,增删效率较高,查询效率较低

2.存储元素方面

ArrayList、Vector、LinkedList中的元素有序、可重复、允许null值

3.扩容方面

ArrayList一次扩容1.5倍

Vector根据增量扩容,增量为0,扩容2倍;否则原容量+增量

LinkedList没有扩容

4.线程安全方面

ArrayList、LinkedList线程不安全(如果有多个线程需要同时访问List集合中的元素,可以考虑使用Collections将集合包装成线程安全的集合

Vector线程安全

猜你喜欢

转载自blog.csdn.net/BlackMaBa/article/details/81235316