1 ArrayList继承关系
ArrayList
的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity
操作来增加 ArrayList
实例的容量。这可以减少递增式再分配的数量。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
-
为什么
ArrayList
继承了AbstractList
还要再实现List
接口?- 增加可阅读性,显式的实现List 接口
- 作者犯了错误,后来也就没有修改
-
RandomAccess
是一个标志接口,表示实现这个接口的List集合是支持快速随机访问的。该接口没有定义任何方法public interface RandomAccess {}
-
实现
Cloneable
,覆盖了clone()
方法,能被克隆。 -
实现
Serializable
接口,意味着支持序列化,能通过序列化传输。
2 扩容机制
以Java11源码进行分析。
2.1 三种构造函数
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // 保存ArrayList数据的数组
private int size; // ArrayList列表的大小,非elementData数组的大小
/**
* Constructs an empty list with the specified initial capacity.
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's iterator.
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// c为空 replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
我们从源码中看到,使用无参构造方法时,实际上初始化赋值的是一个空数组,但为什么代码注释写的是Constructs an empty list with an initial capacity of ten.
呢。实际上,当真正对数组操作时,才真正分配容量。即向数组添加第一个元素时,数组容量扩为10。这会在后续代码中展现出来。
2.2 add
方法
add()
方法添加的是单个元素。
/**
* 在列表尾端添加一个元素 (可供外部调用的接口)
*/
public boolean add(E e) {
modCount++; //
add(e, elementData, size);
return true;
}
/**
* This helper method split out from add(E) to keep method (实际就是add()方法的帮手)
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // 如果数组容量恰好等于List的size
elementData = grow(); // 扩容
elementData[s] = e;
size = s + 1;
}
/**
* 在指定位置插入数据(可供外部调用的接口)
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
我们从源码中可以看出:添加单个数据的add
方法共有三个
- 从尾部添加数据时,实际调用的是内部方法
pivate void add(E e, Object[] elementData, int s)
, 如果elementData
数组容量恰好已满,则用grow()
扩容(2.3节),否则直接在数组中添加数据。 - 在指定位置添加单个数据,用
private void rangeCheckForAdd(int index)
检查,
2.3 grow
方法
private Object[] grow() {
return grow(size + 1);
}
/**
* Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
*/
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
- 源码中共有两个
grow
方法,实际上最后返回的是扩容后新的数组 - 无参
grow()
方法均在添加单个元素而空间恰好满的时候使用,实际上调用了第二个grow(int minCapacity)
方法,告诉其当前最小需要的容量是size + 1
; - 有参
grow(int minCapacity)
方法根据当前数组最小需要的容量minCapacity
来调用newCapacity(minCapacity)
(2.4节)来判断具体应该扩容的多少。
2.4 newCapacity
方法
ArratList扩容的核心方法。(jdk1.8的时候是grow()
方法)
/**
* 返回至少比minCapacity大的容量
* 1.5倍扩容
* Will not return a capacity greater than MAX_ARRAY_SIZE unless
* the given minimum capacity is greater than MAX_ARRAY_SIZE.
*/
private int newCapacity(int minCapacity) {
// oldCapacity为原数组容量,newCapacity扩容为原来的1.5倍
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) { // 如果新容量还小于所需最小容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) //是否原数组是空数组,若是,则返回10或者所需最小容量的较大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // 如果所需最小容量为负,可能是由于addAll方法,导致Integer越界变成负的了
throw new OutOfMemoryError();
return minCapacity;
}
// 如果新容量大于所需最小容量,判断新容量是否超过数组最大长度`MAX_ARRAY_SIZE
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
- 从
newCapacity
可以看出来,当列表第一次add元素时,oldCapacity为0,则newCapacity也为0,小于minCapacity,此时elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,会返回minCapacity和10中的较大值。 - 如果新容量大于
MAX_ARRAY_SIZE
则会执行hugeCapacity()
来比较minCapacity
和MAX_ARRAY_SIZE
3 System.arraycopy 和 Arrays.copyOf
ArrayList中很多地方都用到了这两个方法。
System.arraycopy()
方法
/**
* @param src 源数组.
* @param srcPos 源数组开始位置.
* @param dest 目标数组.
* @param destPos 目标数组开始位置.
* @param length 拷贝的长度
*/
@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
Arrays.copyOf()
方法
/**
* @param 要复制的数组
* @param 要复制的长度
*/
@HotSpotIntrinsicCandidate
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
联系与区别
联系:
Arrays.copyOf()
内部实际调用了 System.arraycopy()
区别:
System.arraycopy()
需要手动传入目标数组,Arrays.copyOf()
是自己创建一个新数组,然后将原数组拷贝进去再返回。
4 ensureCapacity()
方法
ArrayList源码中有一个ensureCapacity()
方法,但很奇怪,在内部方法中却没有得到任何调用,而且声明为public
,似乎是给用户调用的,但好像用户也不怎么用这个方法。
/**
*如有必要,对ArrayList进行扩容
*/
public void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
modCount++;
grow(minCapacity);
}
}
其实,在添加大量元素前,用户可以手动调用ensureCapacity()
方法,以减少数组重新扩容拷贝的开销。
4 modCount
变量
通过源码发现,modCount 字段不容忽视。
protected transient int modCount = 0
参考 Java集合框架学习(4)——ArrayList中的modCount变量
利用迭代器遍历ArrayList的时候,如果用ArrayList.remove方法删除元素,就会引发异常,其实就是modCount
的作用。
其实就是
5 线程安全问题
ArrayList
是List
的主要实现类,底层使用Object[ ]
存储,适用于频繁的查找工作,线程不安全 ;Vector
是List
的古老实现类,底层使用Object[ ]
存储,线程安全的。CopyOnWriteArrayList
(java.util.concurrent包)可以看作是线程安全的ArrayList
,在读多写少的场合性能非常好,远远好于Vector
.
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/**
* The lock protecting all mutators. (We have a mild preference
* for builtin monitors over ReentrantLock when either will do.)
*/
final transient Object lock = new Object();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array; // violatile保证可见性
}