这可能是最详细的ArrayList源码分析
本文内容基于 jdk1.8 环境
本文最先在我的个人博客发布,CSDN为同步发布,如有需要,可以访问腿短快跑的个人博客获取更多内容
源码获取
jdk 源码在我们 jdk 的安装目录下即可找到:
-
jdk1.8
在 jdk1.8 及之前的版本中,jdk的源码均可在安装目录的根目录下找到src.zip
,此包即为 jdk 源码 -
jdk11
从 jdk11 开始,jdk的源码包放在 jdk 安装目录下的 lib 目录下,在 lib 目录下找到src.zip
即为源码
实现接口
ArrayList 实现了以下接口:
-
List
实现了 List 接口,Java集合框架中常用接口 -
RandomAccess
用于实现随机访问功能,因为底层使用数组实现,可以通过下标实现随机访问 -
Cloneable
克隆接口,用于实现两个 List 之间的克隆 -
Serializable
序列化接口
核心属性
// 定义默认List大小private static final int DEFAULT_CAPACITY = 10;// 用于存储实际 ArrayList 中的数据transient Object[] elementData;// 用于存储当前 ArrayList 的大小private int size;
从上述代码中我们可以得出以下结论:
- ArrayList 默认初始化大小为 10
- ArrayList 实际大小使用 size 变量存储
- ArrayList 底层数据使用 Object 数组存储
为什么数组使用transient关键字
transient关键字用于修饰类成员变量,不能用于修饰方法,用于标记对应的类成员变量不是类持久化状态的一部分。
jvm默认序列化和反序列创建字节流的过程中会忽略 transient 关键字修饰的类成员变量
ArrayList 中底层数组使用 transient 修饰是出于性能考虑,因为底层数据结构是数组,因此必然存在很多数组空间未使用的情况,直接序列化将导致序列化性能大大降低和资源的浪费
虽然因为底层数组使用 transient 修饰导致数组不能由jvm默认序列化,但是通过定义writeObject和readObject方法,自定义实现了列表元素的序列化与反序列化。此内容文章后续讲解
核心方法
构造方法
此处以指定大小为例,其他构造方法类似
用于初始化 ArrayList
// 默认空数组private static final Object[] 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); } }
- 如果初始化大小 > 0 ,则创建一个对应大小的 Object 数组
- 如果初始化大小 == 0,则使用默认的静态空数组,此处是为了减少空间占用,避免多个空数组占用内存
- 如果初始化大小 < 0,则抛出异常
indexOf(Object o)
查找元素出现的第一个位置
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; }
- 判断元素是否为空
- 遍历数组列表,找到值相同的索引返回,此处比较是否相同使用的 equals 方法
- 如果未找到对应的元素,返回 -1
lastIndexOf(Object o)
查找元素出现的最后一个位置
public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
- 判断元素是否为空
- 从数组末尾向前遍历,找到第一个值相同的索引返回,同上,使用 equals 方法
- 如果未找到对应的元素,返回 -1
clone()
克隆出一个新的 List
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
- 底层是使用
System.arraycopy
实现的 System.arraycopy
是native方法System.arraycopy
当数组是基础数据类型或者是String
类型的时候是深拷贝System.arraycopy
当数组是非基础数据类型且非String
类型的时候是浅拷贝System.arraycopy
是不安全的
get(int index)
获取指定位置的元素
public E get(int index) { rangeCheck(index); return elementData(index); }
- 进行范围检查,如果出现数组越界,直接抛出异常
- 返回数组对应下标位置的数据
set(int index, E element)
设置指定位置的元素
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
- 进行范围检查,如果出现数组越界,直接抛出异常
- 获取对应索引位置的值
- 将对应索引位置的值设置为新值
- 返回旧值
add(E e)
在 ArrayList 的末尾添加元素
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
- 当前大小+1作为最小数组容量
- 检查容量是否需要扩容,如果需要扩容,则执行扩容操作
- 设置数组后面一个元素为对应的元素
- 返回结果
扩容操作
数组扩容操作
/** * 检查容量,不满足则扩容 */ private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } /** * 计算应该扩容到的容量 */ private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } /** * 判断是否需要扩容 */ private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * 扩容操作 */ 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); }
- 计算当前应该扩容到的容量
a. 数组是否是默认的空数组,是则返回当前最小容量和默认容量(10)的最大值
b. 否则,返回当前最小容量 - 判断是否需要扩容
a. 先对 modCount 增加,modCount 用于标识数组是否被修改过,用于提供快速失败的实现
b. 判断应该扩容到的容量是否超过了当前底层数组的实际大小,如果超过了才需要扩容 - 如果需要扩容则执行扩容
a. 计算出新的数组大小 = 旧的数组的大小 + 旧的数组的大小 / 2,即,扩容后的大小为原数组大小的 1.5 倍,采用 >> 1 的写法是为了提升性能
b. 如果新的数组的大小 < 应该扩容的数组的大小,则新的数组的大小设置为应该扩容的数组的大小
c. 如果新的数组的大小超出了最大数组的大小(Integer.MAX_VALUE - 8 = 2147483639),则新数组的大小设置为 Integer.MAX_VALUE,否则为数组的最大大小(Integer.MAX_VALUE - 8 = 2147483639)
d. 如果扩容的数组的大小出现了数据溢出,则会抛出 OutOfMemoryError 内存溢出异常
e. 创建新的数组,将旧数组数据复制到新的数组中
add(int index, E element)
在指定索引位置插入元素
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++; }
- 范围检查,如果出现数组越界,抛出异常
- 当前大小+1作为数组最小容量
- 检查容量是否需要扩容,如果需要扩容,则执行扩容操作
- 将指定位置的数组内容向后移动一位
- 在指定位置设置数组的元素值
- 数组size+1
remove(int 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; }
- 进行范围检查,如果出现数组越界,直接抛出异常
- 获取对应索引位置的数据
- 计算出需要移动的位置的索引,如果索引 > 0,则将后面的数组的内容向前移动一位
- 将数组的最后一个元素设置为null
- 返回旧数据
remove(Object o)
移除指定元素,仅会移除第一次出现的元素
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 }
- 判断要移除的元素是否为null
- 从开始位置遍历数组元素,判断元素是否相等,如果相等,则执行快速移除(同
remove(int index)
方法的实现),移除后返回结果
clear()
清空 ArrayList
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
- 修改数量+1,用于提供快速失败机制
- 从开始位置遍历数组,将数组中的元素全部设置为null
- 设置 size 大小为 0
addAll(Collection c)
添加集合元素至 ArrayList 结尾
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; }
- 将集合转换为数组
- 当前大小+新数组的大小作为最小容量
- 根据最小容量判断是否需要扩容,如果需要扩容,执行扩容操作
- 将新数组复制到数组的末尾
- size大小更新为当前新的大小
- 返回新增结果
addAll(int index, Collection c)
在指定位置添加集合元素
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开始位置的元素全部向后移动新数组大小的位置
- 将新数组的元素从index位置开始复制
- size大小更新为当前新的大小
- 返回新增结果
removeRange(int fromIndex, int toIndex)
移除指定区间内的元素
protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; }
- 修改数量+1,用于提供快速失败机制
- size-toIndex是需要移动的元素的个数
- 将需要移动的元素移动至 fromIndex 位置
- size - (toIndex-fromIndex) 是现在新的数组的实际大小
- 从新的大小位置开始,将后面的元素全部设置为null
- size设置为新的大小
removeAll(Collection c)
移除包含集合中的所有元素
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); } /** * 批量移除元素 */ 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; }
注意 complement 参数值为 false
- 检查集合元素是否为空,为空则抛出异常
- 遍历数组,如果集合中不包含当前元素(通过 complement 值来判断),则将当前元素依次从 r 位置排列
- 当 r != size 的时候说明 c.contains 可能出现了异常,做了一个兼容操作,此处不详细讲述
- w表示移动后的集合的元素的长度,如果 w != size 则证明存在元素被修改,将 w 位置后面的元素全部设置为 null, 并将 size 设置为 w
- 返回修改结果
retainAll(Collection c)
ArrayList 和 集合取交集
public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); }
注意 complement 参数值为 true
步骤和上面 removeAll
方法实现逻辑一样,只是 complement 参数值不一样,通过 complement 参数来控制是保留包含的元素还是不包含的元素
writeObject & readObject
序列化使用,上面内容我们讲到为了保证性能和避免资源浪费,底层数组使用了
transient
关键字修复,导致无法使用 jvm 的序列化,通过 writeObject 和 readObject 方法实现了 ArrayList 的序列化和反序列化
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(); } } }
总结
从上述源码中我们可以得出以下结论:
- ArrayList也是List 的一种
- ArrayList底层使用 Object 数组实现
- ArrayList比较适合读多写少的情况(写需要挪动元素)
- ArrayList初始容量大小为10
- ArrayList每次扩容大小为原容量的 1.5 倍