List类及其子类相关

163 阅读3分钟

概述

List可能是我们工作中用到的最多的类,我们来看看相关子类的特性

ArrayList

我们来看看ArrayList里面关键的代码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    # 初始化不写容器大小的话,第一次扩容的大小,刚创建的时候是0
    private static final int DEFAULT_CAPACITY = 10;
    # 装容器的数据
    transient Object[] elementData
    
    
    # 添加元素
    public boolean add(E e) {
        # 确保容器大小够装得下
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    # 确保容器能装得下
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

   
    扩容策略
    首先扩容一半  newCapacity = oldCapacity + (oldCapacity >> 1); 由移一位等于除2
    如果还是没有需要扩容的数据大,那么直接扩容到需要扩容的大小
    如果还不行,扩到 Int最大值,基本上用不上
   
    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);
    }

    # 删除元素 null 也可以删除, 只能删除一个
    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;
    }
    

    private void fastRemove(int index) {
        modCount++;
        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
    }

}

需要注意的是两个方法

  1. Arrays.copyOf(elementData, newCapacity); 通过旧数组拷贝出一个新的数组,通常用于扩容
  2. System.arraycopy() 用于两个数组之间的拷贝 通过源码的阅读可以发现,基于底层是连续数组的特性,总结几个特点
  3. 根据下标是在尾部,都需要挪动一部分数组 System.arraycopy 实现
  4. 根据下标访问的时候,时间复杂度是 O(1)
  5. 删除元素的时候,时间复杂度是 O(N)

LinkedList

顾名思义,底层是通过链表来实现

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    # 头指针
    transient Node<E> first;
    # 尾指针
    transient Node<E> last;
    
    # LinkedList 里面存储的全部都是 Node
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}

链表没有扩容的需求,直接通过指针往后指就可以了。基于链表的特性,可以总结出 LinkedList 的特性

  1. 增加和删除元素的时候,如果是头尾添加则时间复杂度是 O(1),如果是按照下标来添加的话,时间复杂度是 O(n/2). 代码会判断距离头近还是尾近,开始遍历
  2. 根据下标访问的时候,时间复杂度是 O(N)
  3. 根据元素删除的时候,时间复杂度也是O(N)

ArrayList 和 LinkedList 对比

对比了两个类的增加删除的特性,数组只有在增加和删除的时候需要挪动数组,但是同时链表也需要遍历元素,所以差不多,而其他方面ArrayList效率都远高于LinkedList。在不考虑内存的情况下,LinkedList可以不用了。

Vector

一个所有方法都被用了 Synchronized 关键字的 ArrayList

CopyOnWriteArrayList

ArrayList 虽然好用,但是它是非线程安全的。线程安全的 Vector又显得有一些笨重。于是 JUC包里面提供了一个类 CopyOnWriteArrayList来实现线程安全的快速读的List。 先看代码,因为他保证了线程安全,所以着重看添加方法


public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    # 锁
    final transient ReentrantLock lock = new ReentrantLock();

    # 容器
    private transient volatile Object[] array
    
    # 读操作完全不用上锁
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    public E get(int index) {
        return get(getArray(), index);
    }
   
    添加数据的时候 Arrays.copyOf(elements, len + 1); 拷贝出一个新的数据,然后添加上去,最后覆盖原来的值
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
}

可以从类名就看出来这个类到底在干什么,写时复制。就是为了保证并发速度,采用了空间换时间的思想。在写的时候拷贝了一个新的数组,同时,用户读取的还是旧的数据,写好了再进行覆盖。

image.png