ArrayList、LinkedList、Vector、CopyOnWriteArrayList原理解析

来源:互联网 发布:银行软件开发招聘 编辑:程序博客网 时间:2024/06/06 18:52

一、List家族特点

集合 效率 线程安全性 ArrayList 读取快,插入慢 线程不安全 LinkedList 插入快,读取慢 线程不安全 Vector 慢 线程安全 CopyOnWriteArrayList 读取快,插入慢 线程安全

二、ArrayList

Java中的数组初始化后,长度就是不可变。简单来说,ArrayList就是可变长数组。
由于ArrayList底层是通过数组来实现的,所以必然要解决下面两个问题。

  1. 初始容量,最大容量
  2. 如何扩容

ArrayList的初始容量不是在初始化时设置的,而是在第一次进行add后者addAll操作时设置的。(这是JDK1.7做的一个优化,老版本中ArrayList在构造完成后容量默认是10)

下面是ArrayList的构造函数,在初始化时,并没有为数组分配空间

private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = {};//存储数据的数组private transient Object[] elementData;public ArrayList(int initialCapacity) {    super();    if (initialCapacity < 0)        throw new IllegalArgumentException("Illegal Capacity: "+                                           initialCapacity);    this.elementData = new Object[initialCapacity];}public ArrayList() {    super();    //初始化时,是空数组    this.elementData = EMPTY_ELEMENTDATA;}public ArrayList(Collection<? extends E> c) {    elementData = c.toArray();    size = elementData.length;    if (elementData.getClass() != Object[].class)        //将elementData拷贝到一个长度为size的新数组        elementData = Arrays.copyOf(elementData, size, Object[].class);}

下面是为ArrayList每次进行add或者addAll操作时分配内存的代码。

private void ensureCapacityInternal(int minCapacity) {    //分配的最小容量=max(默认容量10,第一次插入的元素的个数)    if (elementData == EMPTY_ELEMENTDATA) {        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);    }    ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) {    modCount++;    // overflow-conscious code    //当数组的长度不够,调用grow方法对数组进行扩容    if (minCapacity - elementData.length > 0)        grow(minCapacity);}/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/** * Increases the capacity to ensure that it can hold at    least the number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    //新的容量=以前的容量+以前容量的1/2    int newCapacity = oldCapacity + (oldCapacity >> 1);    //如果newCapacity 比minCapacity 还要小,就分配minCapacity     //这里存在两种情况,一是newCapacity确实比minCapacity小,二是newCapacity 越界了,超过了int的最大值,导致newCapacity 为负数    if (newCapacity - minCapacity < 0)        newCapacity = minCapacity;    if (newCapacity - MAX_ARRAY_SIZE > 0)        //newCapacity超过MAX_ARRAY_SIZE时,根据minCapacity来判断是分配MAX_ARRAY_SIZE还是Integer.MAX_VALUE        newCapacity = hugeCapacity(minCapacity);    elementData = Arrays.copyOf(elementData, newCapacity);}private static int hugeCapacity(int minCapacity) {    if (minCapacity < 0) // overflow        throw new OutOfMemoryError();    return (minCapacity > MAX_ARRAY_SIZE) ?        Integer.MAX_VALUE :        MAX_ARRAY_SIZE;}

int newCapacity = oldCapacity + (oldCapacity >> 1);这句执行后如果超过int的最大值,那么newCapacity会是一个负数。

总结一下ArrayList的扩容算法:
当前容量:oldCapacity
新的容量:newCapacity
此次分配需要的最小容量:minCapacity

当容量不足时,newCapacity=oldCapacity+oldCapacity*1/2,也就是oldCapacity的3/2倍。如果newCapacity发生越界或者newCapacity< minCapacity,那么newCapacity=minCapacity。如果minCapacity>MAX_ARRAY_SIZE,那么最终分配的容量为Integer.MAX_VALUE 或者MAX_ARRAY_SIZE;

三、LinkedList

LinkedList除了实现List接口外,还实现了Deque接口,也就是说,LinkedList除了作为集合外,还可以用作队列。
由于我们讨论的是List家族,所以,下面主要介绍LinkedList作为List中的成员,是如何实现的。

我们都知道,相对于ArrayList,LinkedList有插入更新快、读取慢的特点,这种特性与它底层的数据结构密切相关。

LinkedList底层的数据结构是双向链表。
这里写图片描述
注:LinkedList最开始的数据结构是双向循环链表(只有一个header指针),后面改成了双向链表(一个first指针、一个last指针)

public class LinkedList<E>{    transient int size = 0;    //头节点    transient Node<E> first;    //尾节点    transient Node<E> last;    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插入快,查找慢。
下面是LinkedList插入元素时的代码,可以看出,插入一个元素时newNode时,只需要O(1)的时间复杂度。

public void addLast(E e) {    //插入链表尾部    linkLast(e);}void linkLast(E e) {    final Node<E> l = last;    //新增一个节点,前继为尾节点,后继为null    final Node<E> newNode = new Node<>(l, e, null);    last = newNode;    if (l == null)        first = newNode;    else        l.next = newNode;    size++;    modCount++;}

再看看LinkedList在指定位置插入元素的代码,该操作的复杂度主要在node(int index)方法上,最坏的情况下,会循环size*1/2次,但是相对于ArrayList 的整体后移,还是相当快的。

public void add(int index, E element) {    //校验是否越界    checkPositionIndex(index);    if (index == size)        linkLast(element);    else        //将element插入node节点        linkBefore(element, node(index));}void linkBefore(E e, Node<E> succ) {    // assert succ != null;    final Node<E> pred = succ.prev;    final Node<E> newNode = new Node<>(pred, e, succ);    succ.prev = newNode;    if (pred == null)        first = newNode;    else        pred.next = newNode;    size++;    modCount++;}//找到位置index的node节点,如果index<size*1/2,从前往后找,否则从后往前找Node<E> node(int 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;    }}
原创粉丝点击