ArrayList是JAVA集合类中一个最为基础最为使用普遍的集合,本文将基于JDK1.8来解读ArrayList的源码实现java
ArrayList的基础结构是数组,因此ArrayList和数组具备一样的性质:算法
那么问题来了,数组是固定大小的,ArrayList大小是可变的,内部是如何维护,如何实现的呢。其是从原理上来讲很简单,那就是若是内部数组长度不够了,那就建立一个新的更大的数组,把原来的数据拷过去便可,具体代码如何实现,那就让咱们来详细的看一下源码吧。api
ArrayList继承关系以下图:数组
实现:ArrayList实现了如下几个类:数据结构
RandomAccess:这个类是用来标记使用,具备这个标记的类具有如下特色:能够快速访问,而且使用下标访问会更快。例如ArrayList就实现了这个类,而LinkedList则没有。这个标记在某些状况下会有做用:例如在实现二分查找的时候,若是所传入的集合类实现了这个类则直接使用下标访问元素,使得算法实现效率更高,不然用迭代器来访问元素,会比下标访问更加快。好比Collections里的二分查找方法,就进行了这样的判断:dom
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key); else return Collections.iteratorBinarySearch(list, key); }
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; // 空数组用于给初始化大小为0的状况进行直接赋值,或者是进行清空list操做时候直接进行赋值 private static final Object[] EMPTY_ELEMENTDATA = {}; // 默认容量空数组,用于无参构造函数的初始化中 // 在有了EMPTY_ELEMENTDATA以后还要DEFAULTCAPACITY_EMPTY_ELEMENTDATA的缘由是想要区分 // 这两种状况:1.用无参构造函数初始化的空数组 // 2.用有参构造函数初始化,而且设定数组初始化大小为0的空数组 // 若是不作区分这两种状况就会混在一块儿无法区分,而后会致使在扩容时产生出乎调用者意料的状况 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // ArrayList存储数据的数组,加上了transient进行修饰说明这个数组将不会被序列化 // ArrayList的元素序列化反序列化是靠readObject和writeObject来实现的 transient Object[] elementData; // ArrayList的大小 private int size; }
无参构造函数ide
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
数组容量初始化函数
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
用集合初始化学习
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
其他的两个构造函数比较简单就不赘述了,可是这个构造函数有一点特殊的地方。有人看到源码后,想问这句代码if (elementData.getClass() != Object[].class)
究竟有什么用?咱们看到注释上写了see 6260652,那就去java的官网一探究竟。看到官网给出的bug记录以下:ui
The Collection documentation claims thatcollection.toArray()
is "identical in function" to
collection.toArray(new Object[0]);
However, the implementation of Arrays.asList does not follow this: If created with an array of a subtype (e.g. String[]), its toArray() will return an array of the same type (because it use clone()) instead of an Object[].
If one later tries to store non-Strings (or whatever) in that array, an ArrayStoreException is thrown.
简单翻译下就是collection.toArray()
这个方法按照规范应该等价于collection.toArray(new Object[0])
,可是各个collection
有本身的实现可能会有一些意外状况出现,好比Arrays.asList
,下面有一个例子:
public static void main(String[] args) { String[] a = {"a", "b", "c"}; List l = Arrays.asList(a); System.out.println(l.toArray()); System.out.println(l.toArray(new Object[0])); }
l.toArray()
返回类型是String[]
,l.toArray(new Object[0])
返回值则为Object[]
。若是不加这句代码,内部的elementData就会被赋值为String数组,这是咱们所不想看到的,因此须要多一层判断,保证这个数组的类型永远是Object[]
。固然,这算是一个bug,而且在后续版本中(JAVA9)中被修复了。
说到ArrayList的核心方法,那必须是他的扩容方法grow
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
这个方法是一个私有方法,当内部方法判断须要扩容的时候就会调用这个方法,那咱们来看一下他的扩容机制。
oldCapacity
是elementData的长度(注意elementData的长度是指内部数组的长度,size是list元素的数量,因此这两个数值每每是不一致的)newCapacity
是预计想要达到的数组容量大小,通常状况下是扩容1.5倍,即oldCapacity + (oldCapacity >> 1)
,使用右移运算比乘法效率更高。if (newCapacity < minCapacity)
而要写成if (newCapacity - minCapacity < 0)
,这是由于若是旧有的容量已经够大的话,进行1.5倍扩容后newCapacity
可能已经由于数字过大溢出变成了负数,若是用newCapacity < minCapacity
来比较的话那就会永远是true
,这不符合咱们的预期,咱们的预想应该是既然新的容量已经溢出了,那么咱们应该是取到溢出前的极大值,这么写,就会致使取到minCapacity。而newCapacity - minCapacity
就不同了,即便newCapacity
溢出也不会影响判断。判断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; }
Arrays.copyOf
方法来进行数组的扩容在某些状况下每一次扩容1.5倍显然没法知足咱们的需求,当咱们预计一次性将插入大量数据的时候,频繁的扩容会致使频繁的建立新的数组,而且把旧数据迁移到新的数组之中。这个效率是很是低下的。因此咱们有两种方法解决这个问题:一种就是在建立ArrayList的时候就直接用数组容量来建立数组,可是这种方法因为咱们建立ArrayList
的时候未必开始就会插入这么多数据,因此可能会致使内存的浪费,另外一种方法就是使用ensureCapacity
方法:
public void ensureCapacity(int minCapacity) { // 最小容量扩展数,通常为DEFAULT_CAPACITY,也就是10 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; // 若是须要的扩容大小大于minExpand,则扩容至minCapacity,不然就不进行扩容 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
ArrayList的添加方法以下,共有5种:
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
实现方法都比较简单,有几个地方须要注意一下:
在固定的index插入元素的方法都会在方法最开始调用一个名为rangeCheck
的方法来检查是否越界,方法以下:
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
有人可能会好奇为何要作这个校验,主要的缘由其实在前面说过了,存储元素的数组大小不等于size的大小,举个例子,当你的ArrayList
只有一个元素的时候,你的size为1,可是你的内部数组大小多是default的10,这个校验能够避免你把数据插入到了奇怪的地方。
ensureCapacityInternal
来减小扩容get方法:
public E get(int index) { // 校验是否越界(校验理由同上) rangeCheck(index); // 获取元素 return elementData(index); } E elementData(int index) { return (E) elementData[index]; }
删除方法:
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work } public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); } public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
删除方法没什么特别的地方,无非是找到后删除,而后数组元素移动等,惟一的一个特别的地方是,删除的时候循环中是使用下标循环查找删除,而不是使用迭代器循环,缘由就是,数组特性:使用下标访问更快,这也是为何ArrayList
实现了RandomAccess
序列化方法:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
集合元素存储在数组中在序列化的时候须要特殊处理,否则会有问题,所以把elementData
设置为transient
防止数组被序列化,而且本身实现readObject
和writeObject
来实现一个能够完美处理数组元素的序列化方法
ArrayList
是代码中出现率极高的数据结构,在平常使用中能够说是大量使用,而且本人也自认为对其了如指掌,可是直到慢慢沿袭它的源码,才发现他的不少设计理念以及一些不经常使用api于我来讲实际上是很是陌生的,所以,我一边查资料一边学习它的源码,试图可以学到一些东西。我在此把我阅读源码的一些体会和心得记录下来,但愿可以与你们一块儿学习进步。