接着Collection简单了解之后,开始学习ArrayList,这个集合在工作中比较常用,理解它更便于我们写出更优秀的代码
介绍
- ArrayList是通过动态数组实现的
- 允许存储Null值,这个所有通用的实现都可以存null值
- ArrayList是线程不安全的 毕竟很多情况下都不是多线程操作,锁会增加性能损耗,且不必要的同步可能导致死锁,需要同步集合可以参考java.util.concurrent包提供的并发实现
- 具有fail-fast机制(可以规避 比如ListIterator/CopyOnWriteArrayList)
- 查询快
- ArrayList有初始容量10,它指的是ArrayList在必须增长之前可以容纳的元素数量
- 如果你需要在集合开头的位置添加或删除元素,你应该考虑使用LinkedList,而不是ArrayList
- ArrayList有List特有的迭代器 ListIterator
- 初始默认扩容容量10,在必须增长之前扩容为原容量的1.5倍
这些总结在下面都会通过源码来验证!
继承关系
- AbstractList:AbstractList在上一篇文已经讲解过了
- List:没发现有什么用.毕竟它的父类AbstractList已经实现了List,文末再说这个问题
- RandomAccess:List实现的标记接口,表示支持快速随机访问,这里不多谈论这个了,如果有需要文末再单独讨论吧
- Cloneable:可被克隆
- Serializable:可被序列化
ArrayList成员变量
//默认容量 可在创建ArrayList时通过构造函数指定
private static final int DEFAULT_CAPACITY = 10;
//创建一个长度为0的存放数据的空数组 它很少使用到 当你new一个ArrayList(0)时,它会创建
//这个很少用到 如果你使用它,它的扩容会更频繁 应该没人用...
private static final Object[] EMPTY_ELEMENTDATA = {};
//创建一个长度为0的存放数据的空数组
//当添加第一个元素时 如果elementData指向自己则扩容为DEFAULT_CAPACITY
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数据的数组
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList的实际数据存储数量 就是size()返回的结果
private int size;
常用方法解析 所有代码都不是完整的源码,而是精简过后的源码!结合源码食用更佳
add(E e)
我说这个方法是最常用的没人反对吧? 下面是主要功能
//第一次add默认扩容DEFAULT_CAPACITY 也就是10 minCapacity是当前数组的length+1的长度
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//modCount记录的是 这个列表在结构上被修改的次数(这句话总结的很到位 --)
modCount++;
//判断是否需要扩容 elementData.length就是当前数组的长度
if (minCapacity - elementData.length > 0)
//主要扩容的方法
grow(minCapacity);
//扩容方法 每次扩容为原容量的1.5倍
private void grow ( int minCapacity){
//获取当前数组长度 假设是默认容量已经存满 需要扩容 这里就是10
int oldCapacity = elementData.length;
//newCapacity = 10>>1 = 5 + 10 = 15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//在elementData = EMPTY_ELEMENTDATA时,也就是你在创建ArrayList时指定了默认容量为0 会<0
//如果你使用了 EMPTY_ELEMENTDATA 需要多次扩容后才能达到1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//防止溢出 通常也达不到int的最大取值范围..
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 返回扩容后的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
remove(int index)
作用:移除列表中指定位置的元素。将任何后续元素向左移动(从它们的索引中减去一个)
//这个列表在结构上被修改的次数(这句话总结的很到位 --)
modCount++;
//根据索引直接获取该位置的元素用来在最后返回给调用者
E oldValue = elementData(index);
// 得到需要移位的数据长度 -1是为了去除会被数组copy覆盖的那一个元素
int numMoved = size - index - 1;
//如果数组中zise = 1 直接置为null 那么也没有必须再将index后的元素移位了
if (numMoved > 0)
//从删除的index+1的位置复制所有元素前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将数组最后一位置为null
elementData[--size] = null; // clear to let GC do its work
//返回给调用者删除的元素
return oldValue;
可能有人(其实就是我自己)又要捣鼓为什么elementData[–size] = null,最后一位被置为null,而不是减少数组的长度,那么可不可以再获取到这个null,理论上来说这个位置目前存的是null,原数据会被回收,实际上是没错,但是如果你通过get(index)会直接越界,原因就是–size,虽然数组没有被减少但是size却被减少了,每次get或者remove时都会经过另一个方法判断,如下:
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set(int index, E element)
作用:用指定的元素替换列表中指定位置的元素。返回被替换的数据
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
没写注释 原因是一目了然 ,注意set不会触发modCount++,因为set不属于结构上的修改
get(int index)
return elementData(index);
…
增删改查结束
ArrayList-ConcurrentModificationException
这个涉及到一个ArrayList并发修改的问题,或者说所有继承AbstractList对象的集合都有这个并发修改的问题(没有一个一个看源码,简单看了几个都有),比如Vector/LinkedList等
有几个问题:
- 为什么会触发该异常
- 如何正确处理此异常,或者说让程序避开此异常
第一个问题,为什么会触发该异常
首先需要知道Iterator内部维护了一个expectedModCount,这个变量的作用是就是在你调用iterator()方法时记录下modCount的数量,当程序迭代时会判断expectedModCount是否与modCount一致,代码如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
如果不一致 直接抛出ConcurrentModificationException,在上面的源码中我们分析add与remove方法时可以看到modCount++,在程序迭代中如果触发了modCount++,那么在下一次迭代next方法时如下:
public E next() {
checkForComodification();
}
迭代器循环和for-each循环(底层也是迭代器)在迭代中如果修改了元素都会触发此异常
第二个问题,如何正确处理此异常,或者说让程序避开此异常
1. 使用Iterator的remove方法,原理很简单,代码如下:
//先检查两个变量的值是否一致 不一致抛出异常
checkForComodification();
//调用ArrayList的remove方法 注意此时modCount++ 被触发了
ArrayList.this.remove(lastRet);
//重新赋值 这样下一次next方法checkForComodification()时将不会受到影响
expectedModCount = modCount;
2. 使用listIterator()方法
它继承自Iterator,允许在任意方向上遍历列表,比如向前或向后,并在Iterator的基础上扩展了add/set等不会触发并发异常的方法,原理实现和上面的remove一样就不贴代码
**3.**使用普通的for循环
**4.**使用CopyOnWriteArrayList等更高度封装的集合
扩展知识:
- 可能有人就会捣鼓(其实还是我自己)ArrayList为什么要实现List接口毕竟他的父类AbstractList已经实现List了,折腾了一会,自己按照Collection的设计写了个demo,在简单的使用中没有发现它重复实现的意义,然后想到是不是在其他使用中可能存在特殊意义,比如提升某些场景下的使用效率如代理或反射等,后来才发现是作者的mistake,连接https://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete 当然是否如这个链接所说也无法求证, 如果是链接所说这样那我有个问题 为什么Vector等其他集合也会和父类实现同样的接口呢,它们并不是同一个作者来编写,这可能是设计的规范,而这些作者需要遵循这个规范,可能是为了提升可读性或像我说的有其他特殊作用,当然可以暂时这样认为(毕竟stackoverflow上都这么说),强迫症找不到答案会很难受,舒服…