Java源码系统之ArrayList
一、介绍
java.util.ArrayList<E> 是非常重要的一个类,在代码中广泛使用,E表示泛型,ArrayList是一个泛型类。
从图上可以看出,ArrayList继承了AbstractList,实现了List、Cloneable、Serializable、RandomAccess接口。
二、源码分析
基本属性
/**
* 默认初始容量,大小为10.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空的数组,传入容量为0时使用.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空的数组,传入容量时使用,当容器添加第一个元素时,会进行扩容,具体见add(E e)方法.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 真正存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 要分配的数组的最大值
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
-
DEFAULT_CAPACITY :默认容量为10,也就是通过new ArrayList()创建时的默认容量。
-
EMPTY_ELEMENTDATA :空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。
-
elementData:真正存放元素的地方,使用transient是为了不序列化这个字段。
-
size:真正存储元素的个数,而不是elementData数组的长度。
-
MAX_ARRAY_SIZE:数组长度的最大值是Integer.MAX_VALUE - 8
数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。
如果数组长度过大,可能出现的两种错误
OutOfMemoryError: Java heap space 堆区内存不足(这个可以通过设置JVM参数 -Xmx 来指定)。
OutOfMemoryError: Requested array size exceeds VM limit 超过了JVM虚拟机的最大限制,我的window64就是 Integer.MAX_VALUE-1 .
构造方法之ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果传入的初始容量值大于0,则初始化一个初始容量大小的数组,用来存储元素
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果等于0,则把空数组EMPTY_ELEMENTDATA赋给elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果小于0,则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
构造方法之ArrayList()
public ArrayList() {
//使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在添加第一个元素的时候会扩容
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
构造方法之ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
//传入的集合转为数组,并赋值给elementData
elementData = c.toArray();
//判断传入的集合的大小是否为0
if ((size = elementData.length) != 0) {
//判断集合toArray()返回的结果是否是Object[].class,如果不是,则重新拷贝成Object[].class类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果传入的集合为0,则初始化为空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
方法之add(E e)
/**
* 添加元素
*/
public boolean add(E e) {
//检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//添加元素到最后一位
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果elementData为空数组,则取默认容量和传入的所需最小容量(minCapacity)之中大的值
//并把该值重新赋值给minCapacity
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(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;
//新的容量是老容量1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//如果新容量比所需最小容量小,则把所需最小容量值赋给新的容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果新容量要大于所允许的最大长度,则会使用最大容量为Integer的最大值,
//否则使用MAX_ARRAY_SIZE
newCapacity = hugeCapacity(minCapacity);
//以新容量拷贝出来一个新数组
elementData = Arrays.copyOf(elementData, 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;
}
方法之add(int index, E element)
/**
* 添加元素到指定位置
*/
public void add(int index, E element) {
//检查是否越界
rangeCheckForAdd(index);
//检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将下标为index及之后的元素向后移一位,这样index就空出来了
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将元素插入到index的位置
elementData[index] = element;
//大小加1
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
方法之addAll(Collection<? extends E> c)
/**
* 求两个集合的并集
*/
public boolean addAll(Collection<? extends E> c) {
//将集合c转为数组
Object[] a = c.toArray();
//集合a的大小
int numNew = a.length;
//扩容所需的容量=已有数组的大小+c集合的大小
ensureCapacityInternal(size + numNew); // Increments modCount
//将集合c的元素,拷贝到数组的最后
System.arraycopy(a, 0, elementData, size, numNew);
//size加上c集合的大小
size += numNew;
//如果集合c不为空,返回true,否则返回false
return numNew != 0;
}
方法之get(int index)
/**
* 获取指定位置的元素
*/
public E get(int index) {
//检查是否越界
rangeCheck(index);
//返回数组index的元素
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
注:获取指定索引位置的元素,时间复杂度为O(1)。
方法之remove(int index)
/**
* 删除指定位置元素
*/
public E remove(int index) {
//检查是否越界
rangeCheck(index);
modCount++;
//获取index位置的元素
E oldValue = elementData(index);
int numMoved = size - index - 1;
//如果index不是最后一位,则将index之后的元素往前挪一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
//讲最后一个元素删除,置空,帮助GC回收
elementData[--size] = null; // clear to let GC do its work
//返回旧值,即删除的index对应的值
return oldValue;
}
注:删除指定索引位置的元素,时间复杂度为O(n);而且从源码上可以看出,删除元素后ArrayList并没有缩容。
方法之remove(Object o)
/**
* 删除指定元素值的元素
*/
public boolean remove(Object o) {
if (o == null) {
//遍历整个数组,找到元素第一次出现的位置,并将其快速删除
for (int index = 0; index < size; index++)
//如果要删除的元素为null,则使用==进行比较,满足条件则进行快速删除,并返回true
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//遍历整个数组,找到元素第一次出现的位置,并将其快速删除
for (int index = 0; index < size; index++)
//如果要删除的元素不为null,则使用equals进行比较,满足条件则进行快速删除,并返回true
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
//和remove(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
}
方法之retainAll(Collection<?> c)
/**
* 求两个集合的交集
*/
public boolean retainAll(Collection<?> c) {
//校验集合c是否为null,为null则抛出NullPointerException
Objects.requireNonNull(c);
//调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素
return batchRemove(c, true);
}
/**
* 批量删除
* @param c 集合
* @param complement 为true,表示删除不包含在c中的元素,否则表示删除包含c中的元素
* @return
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
//使用读写两个指针同时遍历数组
//读指针每次自增1,写指针放入元素的时候才加1
//这样不需要额外的空间,只需要在原有的数组上操作就可以了
int r = 0, w = 0;
boolean modified = false;
try {
//遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
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.
//正常来说r最后是等于size的,除非c.contains()抛出了异常
if (r != size) {
//如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//将写指针之后的元素置为空,帮助GC回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//新的大小等于写指针的位置,因为每写一次写指针就加1,所以新大小正好等于写指针的位置
size = w;
//有修改,设置modified=true
modified = true;
}
}
return modified;
}
方法之removeAll(Collection<?> c)
/**
* 删除包含c集合的元素,即求差集
*/
public boolean removeAll(Collection<?> c) {
//检查集合c不能为null
Objects.requireNonNull(c);
//批量删除,complement为false,表示删除包含在c中的元素
return batchRemove(c, false);
}
elementData之序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
//防止序列化期间有修改
int expectedModCount = modCount;
//写出非transient非static属性(会写出size属性)
s.defaultWriteObject();
//写出元素个数
s.writeInt(size);
//依次写出元素
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;
//读入非transient非static属性(会读取size属性)
s.defaultReadObject();
//读入元素个数,没什么用,只是因为写出的时候写了size属性,读的时候也要按顺序来读
s.readInt();
if (size > 0) {
//计算容量
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
//检查是否需要扩容
ensureCapacityInternal(size);
Object[] a = elementData;
//依次读取元素到数组中
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
注:
查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。
一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。
在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。
elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。
总结
-
ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;
-
ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
-
ArrayList添加元素到尾部极快,平均时间复杂度为O(1);
-
ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
-
ArrayList从尾部删除元素极快,时间复杂度为O(1);
-
ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);
-
ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;
-
ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;
-