List源码之ArrayList原理分析
ArrayList 的体系结构
UML类如如下:
说明: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 机制实现的,读写分离的数组结构的集合 。底层采用了锁的机制,实现读的时候不加锁,写的时候加锁。