1. ArrayList
ArrayList扩容机制
扩容规则
-
ArrayList() 会使用长度为零的数组
-
ArrayList(int initialCapacity) 会使用指定容量的数组
-
public ArrayList(Collection<? extends E> c) 会使用 c 的大小作为数组容量
-
add(Object o) 首次扩容为 10,再次扩容为上次容量的 1.5 倍
-
addAll(Collection c) 没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)
2. Iterator
Fail-Fast 与 Fail-Safe
-
ArrayList 是 fail-fast 的典型代表,遍历的同时不能修改,尽快失败
-
CopyOnWriteArrayList 是 fail-safe 的典型代表,遍历的同时可以修改,原理是读写分离
2.1 Fail-Fast源码分析
private static void failFast() {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("A"));
list.add(new Student("B"));
list.add(new Student("C"));
list.add(new Student("D"));
for (Student student : list) {
System.out.println(student);
}
System.out.println(list);
}
当使用增强for遍历Arraylist集合时,底层是使用迭代器遍历集合的,创建迭代器时把集合最开始被修改的次数记录下来,赋值给expectedModCount变量,每次调用next方法时都会检查expectedModCount变量的值是否与集合最开始被修改的次数是否一致,如果不一致,证明在遍历集合的同时被修改了,抛出并发修改异常
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // 返回下一个元素索引
int lastRet = -1; // 返回最后一个元素索引,不存在则返回-1
int expectedModCount = modCount;//把最开始集合被修改的次数赋值给expectedModCount变量。例如集合调用了四次add方法,则集合在遍历之前被修改了四次
Itr() {}
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
......
}
final void checkForComodification() {
if (modCount != expectedModCount)//
throw new ConcurrentModificationException();
}
2.3 Fail-Safe源码分析
private static void failSafe() {
CopyOnWriteArrayList<Student> list = new CopyOnWriteArrayList<>();
list.add(new Student("A"));
list.add(new Student("B"));
list.add(new Student("C"));
list.add(new Student("D"));
for (Student student : list) {
System.out.println(student);
}
System.out.println(list);
}
使用增强for遍历的时候,创建迭代器对象,在迭代器内部记录被遍历的数组,而在添加元素时拷贝旧数组得到一个新数组,操作的是另一个数组,这样的现象就是,遍历的时候是遍历一个数组,添加的时候是操作另外一个数组,实现了读写分离
/** Snapshot of the array */
private final Object[] snapshot;//记录被遍历的数组
/** Index of element to be returned by subsequent call to next. */
private int cursor;
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);//参数是被遍历的数组
}
private COWIterator(Object[] elements, int initialCursor) {
snapshot = elements;//elements当前遍历的数组,赋值给snapshot
cursor = initialCursor;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
而CopyOnWriteArrayList调用add方法添加元素时,复制旧数组,获取一个长度比旧数组长度加一的新数组
,添加元素时填加到了新数组中
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//1.获取原来旧的数组
Object[] elements = getArray();
//2.旧数组的长度
int len = elements.length;
//3.拷贝数组,得到一个新的数组,新数组长度比原来数组长度加一
Object[] newElements = Arrays.copyOf(elements, len + 1);
//4.把正在调用add方法添加的元素添加到新数组的
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
3. LinkedList 对比 ArrayList 的区别
LinkedList
- 底层数据结构基于双向链表,无需连续内存
- 随机访问慢(要沿着链表遍历)
- 头尾插入和删除的性能高
- 占用内存多
ArrayList
- 底层数据结构基于数组,需要连续内存
- 随机访问快(指根据下标访问)
- 尾部插入、删除性能可以,其它部分插入、删除都会移动数据,因此性能会低
- 可以利用 cpu 缓存,局部性原理