概述
ArrayList 实现了 List 接口,是我们经常使用的一个数据集合,我们通常把它当成一个可变长度的树组使用。长度可变是说我们可以在创建 ArrayList 时不指定它的长度,添加数据时只需要添加就行了,ArrayList 会动态的扩充容量。
ArrayList 内部是通过一个对象数组来存储数据元素的:
1 | transient Object[] elementData; |
这种底层存储数据结构决定了ArrayList的添加删除操作效率是不高的,而随机访问效率很高。
数组构造
1 | public ArrayList(int initialCapacity) { |
元素添加和扩容
1 | public boolean add(E e) { |
在真正添加数据前,首先要检查一下当前数组是否可以容纳数据的添加,如果容量不够,就要进行扩容。
1 | private void ensureCapacityInternal(int minCapacity) { |
这里的 ensureCapacityInternal 方法只有在添加数组时才会被调用的,因此,在构造 ArrayList 时你没有制定容量大小,它不会立即把树组大小设置为10,除非有添加操作时才会进行设置。
是否包含元素
1 | public boolean contains(Object o) { |
可见,ArrayList 判断是否包含元素采用的时遍历树组的方法,使用 equals 来判断。
所以我们要重写元素的 equals 方法。
而且,这里的遍历方式为顺序遍历,如果数据量很大,那么就会有很大的性能问题。
删除元素
1 | public E remove(int index) { |
1 | // 元素删除操作也是顺序遍历数据进行 |
1 | // 删除相同元素 |
迭代器
1 | public Iterator<E> iterator() { |
1 | private class Itr implements Iterator<E> { |
这里主要说明一下迭代器的 expectedModCount 变量,它被赋予初始值 modCount,也就是 ArrayList 记录修改次数的变量。在 next() 和 remove() 都回去检查 modCount 和 expectedModCount 变量是否相等,否则就抛出 ConcurrentModificationException 异常。
对于这个异常我们可能并不陌生。比如下面的用法就会抛出这个异常:
1 | Iterator iterator = list.iterator(); |
在上面的用法中,在使用迭代器遍历集合的时候同时调用集合的方法修改集合元素。这回导致 modCount != expectedModCount
不相等,从而抛出异常。
那么正确的做法应该要怎么样呢?
1 | Iterator iterator = list.iterator(); |
因为在迭代器的 remove 方法中,在调用完 ArrayList 的 remove 方法后,会重新对 expectedModCount 赋值,从而保持 expectedModCount == modCount。