ArrayList源码分析 Hey 大家好,我是 Shio👋。今天我们将深入探讨 Java
的ArrayList
源码, ArrayList
是Java提供的动态数组容器, 和Java中数组相比, 它能够动态增加长度。
其继承了AbstractList
, 并且实现了List
, RandomAccess
, Cloneable
, Serializable
接口
public class ArrayList <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable{ }
这些接口代表的含义如下:
List
代表他是一个列表, 支持添加,删除, 查找操作, 可存放重复元素
RandomAccess
是一个标记接口, 代表实现类支持随机访问(根据元素序号快速获取元素对象)
Cloneable
是一个标记接口, 代表实现类具有拷贝能力(可以调用Object.clone()
方法进行拷贝)
Serializable
是一个标记接口, 代表实现类可以进行序列化操作(可以转化为字节流进行持久化或者网络传输),同时其ArrayList
也实现了readObject()
和writeObject()
方法自定义序列化(后面会讲)
源码解析 扩容机制 我们先从最高频的扩容开始
先从ArrayList
初始化说起, ArrayList
有三种方式来初始化
无参, 初始容量设为10, 赋值一个默认容量为空的数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA
)
携带初始容量参数, 如果>0, 直接初始化一个该容量的数组, = 0赋值为空数组(EMPTY_ELEMENTDATA
), <0 抛出非法参数异常
携带Collection
集合, 调用集合的toArray
数组方法, 直接赋值为转化后的数组, 之后进行判断, 如果转化后数组>0, 但数组类型不为Object数组则调用Arrays.copyOf()进行转换
,否则赋值空数组(EMPTY_ELEMENTDATA
)
// 此处不理解为什么要判断数组类型可以看这篇博客
elementData.getClass() != Object[].class
private static final int DEFAULT_CAPACITY = 10 ;private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};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 ) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this .elementData = EMPTY_ELEMENTDATA; } }
可见, 如果以无参构造创建ArrayList
, 实际初始化为一个空数组, 而之后只有到第一次真正添加元素时, 才会真正分配容量, 将数组扩容为10
扩容的起始点是发生在add
方法中
每次次增加元素前都会进行ensureCapacityInternal(size + 1)
操作, 该方法用于在合适时机进行扩容
public boolean add (E e) { ensureCapacityInternal(size + 1 ); elementData[size++] = e; return true ; }
我们进入ensureCapacityInternal(size + 1)
, 发现其实际调用了ensureExplicitCapacity()
方法
当然在调用之前还调用了一个calculateCapacity
这个方法主要是用来判断ArrayList
是否是调用无参构造实例化的, 如果是的话, 就和选择返回默认容量, 否则就是传入的最小容量
其实这里也解答了一个问题就是为什么要定义两个空数组
EMPTY_ELEMENTDATA
是初始化时指定大小为0是赋值的, 之后会一步一步按照1.5倍慢慢扩容
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
则是无参默认赋值的, 在第一次增加元素时,会通过calculateCapacity
以DEFAULT_CAPACITY
(10)进行扩容
private static final Object[] EMPTY_ELEMENTDATA = {};private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
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; }
我们接着跟踪, 来到了 ensureExplicitCapacity
, 这里只进行了两个操作, 增加修改次数, 还有判断是否需要扩容
private void ensureExplicitCapacity (int minCapacity) { modCount++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); }
终于来到了最核心的方法grow()
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ;private void grow (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
由此我们可以梳理出整个扩容的逻辑, 影响的几个分支
第一个是在构造方法中, 选择赋值的是EMPTY_ELEMENTDATA
还是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
第二个是在calculateCapacity()
方法中的if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity);
, 如果是无参默认构造方法赋的初值(也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
) 那么返回的minCapacity
就是
第三个是在grow()
方法中的if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
确认最终选择是扩容大小 为 数组长度的1.5倍大小 还是 方法传入的 minCapacityInternal
面试总结 我在看ArrayList
扩容机制,
从他创建对象, 一开始选择的构造函数就开始决定之后的扩容策略了
ArrayList
源码中有两个成员变量, 一个是EMPTY_ELEMENTDATA
(空元素数据)一个是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
(默认容量空元素数据),这两个都是大小为0的空数组
他俩的区别是什么呢
如果说new ArrayList
对象时选择的是默认空参构造方法, 则赋值 默认容量空元素数据
, 如果是确定容量为0, 或者是传入一个空的Collection
集合, 那么就会赋值为空元素数据
, 具体怎样影响扩容策略我们后面说
扩容机制发生在每次增加元素时, 比如add
或者addAll
方法中, 在增加元素之前都会调用ensureCapacity
方法,通过这个方法选在合适的时机扩容合适的大小, 在传入参数时当前输入 + 1
在这个方法里会调用calculateCapacity()
, 如果ArrayList
在构造时,为默认空参构造, 他就在让传入的当前数组大小 + 1和DefaultCapacity
中选择最大的作为minCapacity
这个minCapacity
在最后的扩容方法grow()
中会当前数组大小的1.5倍进行比较, 选择最大的作为最终的扩容大小
也就是说, 如果你用默认构造函数第一次扩容时,他会给你扩容到10的大小, 如果不是, 而是指定大小为0, 那么就会扩容到 1 的大小(1 -> 2 -> 3 -> 4 -> 6 -> 9 -> 13)会多出很多次扩容, 我想这么做的原因, 可能时源码作者考虑到有些时候不需要10这么大的容量或者扩容机制, 所以如果使用者指定容量大小的话, 那么扩容机制就按照比较 数组容量 + 1 和 数组当前大小1.5的最大值的方式慢慢扩容
缩容机制 既然ArrayList
有默认的缩容机制, 那么是否有缩容机制呢?
答案是没有
无论remove
还是clear
都不会改变现有数组的大小, 而是将数组的相应位置元素设置为null, 便于垃圾回收器回收, 如果想要缩容, 必须手动去掉哟trimToSize
方法, 达到缩容的目的
public void trimToSize () { modCount++; if (size < elementData.length) { elementData = (size == 0 ) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
源码概览 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 Object[] elementData; private int size; 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 () { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList (Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0 ) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this .elementData = EMPTY_ELEMENTDATA; } } public void trimToSize () { modCount++; if (size < elementData.length) { elementData = (size == 0 ) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } public void ensureCapacity (int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; 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++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ; private void grow (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity (int minCapacity) { if (minCapacity < 0 ) throw new OutOfMemoryError (); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } public int size () { return size; } public boolean isEmpty () { return size == 0 ; } public boolean contains (Object o) { return indexOf(o) >= 0 ; } 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 ; } 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 ; } public Object clone () { try { ArrayList<?> v = (ArrayList<?>) super .clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0 ; return v; } catch (CloneNotSupportedException e) { throw new InternalError (e); } } public Object[] toArray() { return Arrays.copyOf(elementData, size); } @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0 , a, 0 , size); if (a.length > size) a[size] = null ; return a; } @SuppressWarnings("unchecked") E elementData (int index) { return (E) elementData[index]; } public E get (int index) { rangeCheck(index); return elementData(index); } 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 ); elementData[size++] = e; return true ; } public void add (int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1 ); System.arraycopy(elementData, index, elementData, index + 1 , size - index); elementData[index] = element; size++; } 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 ; 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 ; } public void clear () { modCount++; for (int i = 0 ; i < size; i++) elementData[i] = null ; size = 0 ; } public boolean addAll (Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); 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); 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 ; } protected void removeRange (int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); int newSize = size - (toIndex - fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null ; } size = newSize; } 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)); } private String outOfBoundsMsg (int index) { return "Index: " + index + ", Size: " + size; } public boolean removeAll (Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false ); } public boolean retainAll (Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true ); } public ListIterator<E> listIterator (int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException ("Index: " + index); return new ListItr (index); } public ListIterator<E> listIterator () { return new ListItr (0 ); } public Iterator<E> iterator () { return new Itr (); }