List源码之ArrayList原理分析

101 阅读3分钟

List源码之ArrayList原理分析

ArrayList 的体系结构

UML类如如下:

image.png

说明:ArrayList 是List的其中一个实现。其中List接口继承 Collection接口,Collection 接口什么了集合的基本操作,如判断集合大小,判断集合是否空集合,集合的迭代遍历,集合和数组的转换,集合原则的增删改查等操作。 1.8版本的jdk中开始增加了stream的操作。

ArrayList 底层数据结构

1、ArrayList 底层采用数组实现。

数组是线性表结构,它的特点是内存连续,可以通过索引下标访问,访问和遍历很方便。

读取元素的时间复杂度是 O(1) ;

查找元素的时间复杂度是 O(n);

删除元素的时间复杂度是O(n),其中涉及元素的移动;

插入元素的分2种,一种是在指定位置前后添加,涉及元素移动,另一种是在尾部(当前集合中最后一个元素)添加。

2、ArrayList 的类属性说明

1)DEFAULT_CAPACITY 默认容许大小;

2)elementData 记录集合中的元素,Object 类型的数组

3)size 记录集合中元素的个数

// ArrayList 默认的初始容量大小,默认是10
private static final int DEFAULT_CAPACITY = 10;

/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 记录集合中的元素
 */
transient Object[] elementData; // non-private to simplify nested class access

// 记录集合中元素的个数,在add remove 等操作时候回同步变化
private int size;

ArrayList 的初始化

ArrayList 的构造函数,它是一个重载方法,可以选择默认的初始化方式,容量大小默认是10,也可以通过主动指定它的容许大小,也可以通过一个集合对象 Collection的实现来初始化 ArrayList 。


// 默认容许大小是10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity) {}

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

ArrayList的扩容机制

当调用add方法区分几种场景

1) 当前集合是空的,添加的是第一个元素

2) 当前集合非空,并且容量还有很大富余,

3) 当前集合非空,同时,容量不富余,再添加就不够了,此时会扩容,新容量是原来的1.5倍

4) 当前集合非空,同时容量不富余,并且JVM的堆内存空间捉襟见肘了,扩容1.5倍会出现OOM。


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


private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(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);
}

ArrayList扩容时的数据拷贝

ArrayList的线程安全问题

ArrayList 是非线程安全的。

Vector 是线程安全的集合,使用的是 synchronized 修饰类的行为方法 。

相比较,synchronized 的锁机制下保证线程安全,性能损耗大,可以采用读写锁的方式,读的时候不加锁,写的时候加锁,这样实现效率会更好写,可以参见 CopyOnWriteArrayList 的实现。

ArrayList的遍历

1、 采用 fail-fast机制

当在遍历的时候,发现元素被修改了,快速失败。抛出ConcurrentModificationException 异常。

实现思想: 一个计数 modCount ,每次操作集合元素,modCount 都会++。在迭代遍历的时候会先记录 modCount 为一个期望的值,遍历结束后,期望的值和最新的modCount 比较,有差异则抛出异常 。

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    // 记录期望的值,等于当前最新的操作数
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    // 在迭代遍历的过程中, modCount 可能发生改变,和期望值对比,如果不一致说明是被操作过了,抛出 ConcurrentModificationException
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

ps : CopyOnWriteArrayList 是采用 fail-safe 机制实现的,读写分离的数组结构的集合 。底层采用了锁的机制,实现读的时候不加锁,写的时候加锁。