ArrayList源码解析
Java集合类是Java语言中常用的一个包,包括了List,Map,Set等接口,对于这些常用接口的实现类,我们需要了解它们的数据结构以及特性,以便于选择最合适的集合来存放数据,提高效率。
ArrayList概述、特性
1 ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。
2 底层使用数组实现
3 该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量增长大约是其容量的1.5倍,这种操作的代价很高。(如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。)
4 remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC
5 采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
源码分析
继承结构
RandomAccess接口:这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList。而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。
Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。
Serializable接口:实现该序列化接口,表明该类可以被序列化。
属性
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 版本号 private static final long serialVersionUID = 8683452581122892189L; // 缺省容量 private static final int DEFAULT_CAPACITY = 10; // 空对象数组 private static final Object[] EMPTY_ELEMENTDATA = {}; // 缺省空对象数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 元素数组 transient 意味着序列化的时候此字段不会被序列化 transient Object[] elementData; // 实际元素大小,默认为0 private int size; // 最大数组容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; }
构造方法
public ArrayList() public ArrayList(int) public ArrayList(Collection<? extends E>) public ArrayList() { super(); //调用父类中的无参构造方法,父类中的是个空的构造方法 this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:是个空的Object[], 将elementData初始化。空的Object[]会给默认大小10,等会会解释什么时候赋值的。 }
三个构造方法。无参构造方法,初始化数组大小的在第一次扩容的时候,默认为10。
核心方法
add()
add(E) public boolean add(E e) { //确定内部容量是否够 ensureCapacityInternal(size + 1); // Increments modCount!! //在数据中正确的位置上放上元素e,并且size++ elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { //看,判断初始化的elementData是不是空的数组,也就是没有长度 //因为如果是空的话,minCapacity=size+1=1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是这里,还没有真正的初始化这个elementData的大小。 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //确认实际的容量 ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) //arrayList能自动扩展大小的关键方法就在这里了 grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; //将扩充前的elementData大小给oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity if (newCapacity - minCapacity < 0)//这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作。 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity newCapacity = hugeCapacity(minCapacity);//新的容量大小已经确定好了,就copy数组,改变容量大小咯。 elementData = Arrays.copyOf(elementData, newCapacity); } //这个就是上面用到的方法,很简单,就是用来赋最大值。 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
remove()
remove(int) public E remove(int index) { rangeCheck(index);//检查index的合理性 modCount++;//这个作用很多,比如用来检测快速失败的一种标志。 E oldValue = elementData(index);//通过索引直接找到该元素 int numMoved = size - index - 1;//计算要移动的位数。 if (numMoved > 0) //这个方法也已经解释过了,就是用来移动元素的。 System.arraycopy(elementData, index+1, elementData, index, numMoved); //将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。 elementData[--size] = null; // clear to let GC do its work //返回删除的元素。 return oldValue; }
get()
public E get(int index) { // 检验索引是否合法 rangeCheck(index); return elementData(index); } E elementData(int index) { return (E) elementData[index]; }
indexOf()
// 从首开始查找数组里面是否存在指定元素 public int indexOf(Object o) { if (o == null) { // 查找的元素为空 for (int i = 0; i < size; i++) // 遍历数组,找到第一个为空的元素,返回下标 if (elementData[i]==null) return i; } else { // 查找的元素不为空 for (int i = 0; i < size; i++) // 遍历数组,找到第一个和指定元素相等的元素,返回下标 if (o.equals(elementData[i])) return i; } // 没有找到,返回空 return -1; }
set()
public E set(int index, E element) { // 检验索引是否合法 rangeCheck(index); // 旧值 E oldValue = elementData(index); // 赋新值 elementData[index] = element; // 返回旧值 return oldValue; }
modCount的作用:
使用迭代器循遍历ArrayList时,用于检测是否在迭代过程中被修改,即检测ConcurrentModificationException。
1 在ArrayList的所有修改方法中,modCount都会被加1
2 创建迭代器时,会把当前的modCount保存在迭代器的expectedModCount字段中
3 每次迭代时,都会检测迭代器中expectedModCount与modCount是否相等
4 在迭代器中的remove方法中,会在删除元素之后再同步一次modCount