1 类定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList继承AbstractList类,实现了List、RandomAccess、Cloneable、Serializable四个接口。
实现RandomAccess接口,可以实现随机访问。
实现Cloneable,并重写了clone方法,实现ArrayList的拷贝。
实现Serializable接口,可以实现序列化。
2 clone复制
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);
}
}
是一种浅复制,数组中的对象只是复制了引用,并没有复制对象本身。
3 构造方法
存在三个构造方法
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) {
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 {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
空参构造,设置ArrayList中的Object数组为空数组。提供容量的构造可以指定初始化容量,如果为0则和空参构造一样,小于0则报异常。还有一个构造方法是将整个Collection赋值给ArrayList。
4 添加元素
add也有三个方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
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;
}
第一个调用第二个,如果到达容量的最大值,则扩充容量,如果没有则在数组中添加。第三个方法是在指定索引处添加元素,首先校验索引的合法性,也是如果达到容量最大值则扩充容量,然后将索引后面的所有元素往后挪一位,并插入当前要插入的元素。
5 容量增长
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
首先看第一种情况,无参构造,第一次添加元素时。此时minCapacity=1,oldCapacity=0,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以会走else。
DEFAULT_CAPACITY=10,所以会新建一个10个元素大小的Object数组。
然后看第二种情况,就是无参构造,并且已经达到最大容量10了怎么扩容。
此时调用ArraysSupport.newLength计算新容量
public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength(oldLength, minGrowth);
}
}
oldLength是扩容前的最大容量,minGrowth固定值为1,prefGrowth是oldLength的一半,因为之前采用了右移运算符计算。
因为如果oldLength很大的话,再加上自己的一半可能会溢出,所以后面加了判断,如果在合法的范围内,则直接返回oldLength加他的一半,即按1.5倍扩容。判断合法的范围是1到Integer最大值减去8。
如果溢出了则调用hugeLength
private static int hugeLength(int oldLength, int minGrowth) {
int minLength = oldLength + minGrowth;
if (minLength < 0) { // overflow
throw new OutOfMemoryError(
"Required array length " + oldLength + " + " + minGrowth + " is too large");
} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
return SOFT_MAX_ARRAY_LENGTH;
} else {
return minLength;
}
}
这就是每次扩容只是容量加1。
总结:
1、ArrayList空参构造,没有初始化容量,初始容量为0
2、添加第一个元素时初始化容量为10
3、当添加元素达到数组容量上限时开始扩容,默认扩容1.5倍。
4、如果扩容1.5倍超出最大上限Integer最大值-8,则扩容量为1
5、如果超过Integer最大值,则报错误
6 代码示例
6.1 无参构造
import java.util.List;
public class ArrayLIstDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
try {
System.out.println(getCapacity(list));
} catch (Exception e) {
e.printStackTrace();
}
}
public static int getCapacity(ArrayList<Integer> list) throws NoSuchFieldException, IllegalAccessException {
Class<? extends List> listCls = list.getClass();
Field field = listCls.getDeclaredField("elementData");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(list);
return objects.length;
}
}
通过反射来获取ArrayList容量。jdk17中,会报异常,原因是高版本的jdk中只允许反射public的成员,需要加上虚拟机参数:
--add-opens
java.base/java.lang=ALL-UNNAMED
--add-opens
java.base/java.util=ALL-UNNAMED
--add-opens
java.base/java.nio=ALL-UNNAMED
--add-opens
java.base/sun.nio.ch=ALL-UNNAMED
上面的执行结果为
0
添加第一个元素
list.add(123);
输出结果为
10
验证了无参构造后,第一次添加元素时,初始化为默认大小10。
添加11个元素到list中,查看容量,输出为
15
扩容为1.5倍
6.1 初始化容量构造
ArrayList<Integer> list = new ArrayList<>(3);
输出容量为
3
添加4个元素,输入容量为
4
6.3 扩容优化
如果能够预估使用的数组容量,可以先调用ensureCapacity初始化一个特定的数组容量,避免ArrayList频繁扩容,ArrayList的扩容涉及到数组拷贝到一个扩容后的新数组,比较消耗性能。
7 ArrayList的序列化问题
ArrayList实现了Serializable接口,可以实现序列化,而存储元素的Object数组却是用transient修饰的。
实现Serializable接口的类,在序列化时是自动的,而被transient修饰的变量不会被序列化。那么ArrayList的序列化是通过什么实现的呢?
ArrayList拥有自己的writeObject和readObject方法,可以实现存储元素的序列化。
transient Object[] elementData;
@java.io.Serial
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 behavioral 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();
}
}
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// like clone(), allocate array based upon size not capacity
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
Object[] elements = new Object[size];
// Read in all elements in the proper order.
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
}
elementData = elements;
} else if (size == 0) {
elementData = EMPTY_ELEMENTDATA;
} else {
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}
通过ObjectOutputStream序列化ArrayList的时候,会调用到WriteObject方法,首先写入数组个数,然后逐个写入元素。读取也是一样的道理。
不采用原生的序列化原因应该是为了减少空间占用,因为Object数组的容量和实际存储的元素个数size通常不一致。
8 总结
- ArrayList继承AbstractList,实现了List接口
- 实现了RandomAccess接口可实现随机访问
- 实现了Cloneable接口,并重写了clone方法,实现元素的浅拷贝
- 实现了Serializable接口,可序列化,序列化通过自定义writeObject、readObject来实现序列化,可以减少空对象的序列化。
- ArrayList无参构造创建的Object数组是0长度的,第一次add时,初始化为默认长度10。
- ArrayList可以使用指定长度的构造方法来创建一个指定长度的数组。
- ArrayList通常扩容策略是1.5倍扩容,如果1.5倍扩容后大于最大容量或者溢出,则每次扩容一个。