学习目标
- 掌握ArrayList的扩容机制。
- 掌握Iterator的fail-fast和fail-safe机制。
扩容机制:
- 使用ArraryList()无参构造的时候,会使用长度为0的数组。
- 使用ArrayList(int initialCapacity)构造时,会使用指定容量的数组。
- 使用public ArrayList(Colection<? extends E> c)的时候,会使用c的大小作为数组容量。
- 使用ArrayList 调用add(Object o)方法,首次扩容会从0扩容成10,再次扩容会扩容到上一次容量的1.5倍,比如0,10,15,22,33…….
- 调用addAll(Colection c)方法时,当前没有元素(容量为0的话),首次扩容会max(10,实际元素个数)(从10和实际加入元素个数中选择最大容量进行扩容),当前存在有元素时,则max(原容量1.5倍,实际元素个数)
fail-fast和fail-safe机制
文字说明:
- fail-fast:一旦发现遍历的同时其他人来修改,立即抛出错误(ConcurrentModificationExecption)
- fail-safe:发现便利的同时其他人来修改,应当有应对策略,比如牺牲数据一致性保证遍历的正常执行。
fail-fast的实现原理
当进行ArrayList遍历的时候,会先创建一个迭代器。
其中,cursor为下一个元素的返回值,lastRet为最后一个元素的索引,-1表示结束。
modCount是集合被修改的次数。
expectedModCount为迭代器修改次数,迭代器初始化时会等于modCount。
当迭代器执行next()时,会调用一个CheckForComdification()方法判断,这个方法会比较expectedModCount和modCount的值,如果两者不相等,证明集合在遍历期间元素发生了修改,这时候会抛出ConcurrentModificationException异常。
fail-safe的实现原理
当CopyOnWriteArrayList进行遍历的时候,会先创建一个迭代器。
这个时候,会现将原来的数组复制一份放至snapshot中。
cursor是遍历时的元素下标,当cursor <snapshot.length时,表明仍有下一个元素,也就是hashNext方法的实现原理。
CopyOnWriteArrayList在添加元素的时候,是先获取当前的元素,然后List大小加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();
}
}
但是,CopyOnWriteArrayList在遍历的时候,是对snapshot进行遍历,所以遍历的时候List的元素发生了改变,并不会对当前遍历造成影响,因此也不会报错。
总结
1.ArrayList就是fail-fast典型的代表,遍历的同时不能修改,一旦发生修改则尽快失败。
2.CopyOnWriteArrayList是fail-last的典型代表,遍历时同时可以修改,原理是读写分离