摘要
主要是分析java.util.Arryalist类的相关源码原理。
Arraylist的定义
ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。ArrayList继承AbstractList 并且实现了List和RandomAccess,Cloneable, Serializable接口。List 接口定义了实现该接口的类都必须要实现的一组方法,如下所示,下面我们会对这一系列方法的实现做详细介绍。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable


ArrayList的字段属性
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList的构造函数
无参构造函数
此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0,而不是大家以为的 10。注意:根据默认构造函数创建的集合,ArrayList list = new ArrayList();此时集合长度是0.
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_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);
}
}
初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组并赋给赋给elementData;当等于0时,将空的数组实例EMPTY_ELEMENTDATA 赋给elementData;当小于0时,抛出异常。
泛型参数有参构造函数
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素。?是“任意类”的意思,extends继承不多说,E是指定类型。
/**
* 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 {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList添加元素
- 当通过 ArrayList() 构造一个空集合,初始长度是为0的,size也为0,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置,下标为0的位置。
- 第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
- 第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
- 当第一次扩容容量 oldCapacity + (oldCapacity >> 1)大于最大集合大小Integer.MAX_VALUE - 8时,判断size+1和Integer.MAX_VALUE - 8的大小,第一次size+1肯定小,这时扩容到Integer.MAX_VALUE - 8
- 当集合size=Integer.MAX_VALUE - 8时,然后添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。
- 第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
注意:能向集合中添加 null 的,因为数组可以有 null 值存在。
//添加元素
public boolean add(E e) {
//添加元素之前,首先要确定集合的大小,如果集合不够装则需要扩容
//第一次size=0
ensureCapacityInternal(size + 1);
//集合size位置赋值,先引用size的值,再对size自增
elementData[size++] = e;
return true;
}
//扩容函数
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//第一次添加,空集合,10和1取最大值,所以第一次添加元素初始集合length为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//扩容判断
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果size+1大于集合的大小,说明刚好集合已经满了,则需要扩容
//size 是集合实际元素个数,elementData.length是集合长度,元素没满,用null填满
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
//集合的容量
int oldCapacity = elementData.length;
//>>1表示右移位,相当于除以2的n次方,这里除以2的1次方,相当于容量增加原来容量的一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//扩容一半后还比minCapacity小,那就只有oldCapacity=0的情况了,也就是第一次添加元素,集合大小为10
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
//扩容容量大于最大集合大小
newCapacity = hugeCapacity(minCapacity);
//数组的复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//Integet.MAX_VALUE+1时就会溢出,这时会变成负数,Integer.MAX_VALUE+1=Integer.MIN_VALUE
if (minCapacity < 0)
throw new OutOfMemoryError();
//size+1比最大集合大小还大就取最大整数,否则取最大集合大小
//所以先扩容到MAX_ARRAY_SIZE,下一次扩容就扩容到Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
// 判断类型是否为Object,是Object,生成一个大小为newLength类型为Object的数组实例
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);// 类型不为Object,通过反射重新生成一个大小为newLength的新类型数组实例
///将原数组内容拷贝到新数组中,新数组取最小的数组长度,这里指copy的数组的长度,是原数组的长度
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
//native关键字表示系统方法,Java可以通过JNI来调用其他语言(主要还是C/C++语言)编写的方法
/*Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest: 目标数组
int destPos: 目标数组的开始起始位置
int length : 要copy的数组的长度
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
ArrayList查找元素
就是当相当于是便利数组的方式
public E get(int index) {
//检查索引是否越界,越界则抛出异常
rangeCheck(index);
//直接返回处于该下标位置的数组元素
return elementData(index);
}
ArrayList修改元素
public E set(int index, E element) {
//判断索引是否越界
rangeCheck(index);
//获取原数组指定索引的元素
E oldValue = elementData(index);
//将指定索引处的元素替换为 element
elementData[index] = element;
return oldValue;//返回原数组索引元素
}
ArrayList删除元素
remove(Object o)方法是删除第一次出现的该元素。然后通过System.arraycopy进行数组自身拷贝。注意并不是删除所有该元素
通过索引位置删除
public E remove(int index) {
rangeCheck(index);//判断给定索引的范围,超过集合大小则抛出异常
modCount++;
E oldValue = elementData(index);//得到索引处的删除元素
int numMoved = size - index - 1;
if (numMoved > 0)//size-index-1 > 0 索引处不是最后一个元素
//将数组elementData 的下标index+1之后长度为 numMoved的元素拷贝到从index开始的位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //将数组最后一个元素置为 null
//返回删除的元素
return oldValue;
}
直接删除指定元素
public boolean remove(Object o) {
if (o == null) {
//如果删除的元素为null
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
//return第一表示返回值,第二表示中止函数往下执行
return true;
}
} else {
//不为null,通过equals方法判断对象是否相等
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
}
ArrayList遍历集合
interator方法返回一个 Itr 对象,这个类是 ArrayList 的内部类。
逻辑上讲,迭代时可以添加元素,但是一旦开放这个功能,很有可能造成很多意想不到的情况。
比如你在迭代一个ArrayList,迭代器的工作方式是依次返回给你第0个元素,第1个元素,等等,假设当你迭代到第5个元素的时候,你突然在ArrayList的头部插入了一个元素,使得你所有的元素都往后移动,于是你当前访问的第5个元素就会被重复访问。
java认为在迭代过程中,容器应当保持不变。因此,java容器中通常保留了一个域称为modCount,每次你对容器修改,这个值就会加1。当你调用iterator方法时,返回的迭代器会记住当前的modCount,随后迭代过程中会检查这个值,一旦发现这个值发生变化,就说明你对容器做了修改,就会抛异常。 迭代时不能调用 ArrayList.remove() 方法,可以调用迭代器的remove() 方法删除元素:
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; //游标,下一个要返回的元素的索引
int lastRet = -1; //上一次返回元素的索引; 如果没有这样的话返回-1.
int expectedModCount = modCount;//这个值是在用户调用ArrayList的iterator方法时候确定的
public boolean hasNext() {
//通过 cursor != size 判断是否还有下一个元素
//下面每次获取一个元素cursor都要+1
return cursor != size;
}
public E next() {
//迭代器进行元素迭代时同时进行增加和删除操作,会抛出异常
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;//游标向后移动一位
return (E) elementData[lastRet = i];//返回索引为i处的元素,并将 lastRet赋值为i
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);//调用ArrayList的remove方法删除元素
cursor = lastRet;//游标指向删除元素的位置,本来是lastRet+1的,这里删除一个元素,然后游标就不变了
lastRet = -1;//lastRet恢复默认值-1,因为上一个返回的元素已经删除了
expectedModCount = modCount;//expectedModCount值和modCount同步,因为进行add和remove操作,modCount会加1
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
//expectedModCount这个值是在用户调用ArrayList的iterator方法时候确定的
//前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的
//也就是说不能在迭代器进行元素迭代时进行增加和删除操作,否则抛出异常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList序列化
ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。假如elementData的长度为10,而其中只有5个元素,那么在序列化的时候只需要存储5个元素,而数组中后面5个元素是不需要存储的。于是将elementData定义为transient,避免了Java自带的序列化机制,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
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;
s.defaultReadObject();
s.readInt();
if (size > 0) {
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
ArrayList的线程安全问题
多个线程操作同一个list会出现并发修改异常(java.util.ConcurrentModificationException)
Vector类:用Vector类来实现,它的add()方法加了synchronized关键字修饰,所以能保证线程安全。
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}).start();
}
}
Collections.synchronizedList()方法:利用集合辅助类来创建一个线程安全的集合,这个就是相当于在外面套了一层,使其保证线程安全。
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}).start();
}
}
CopyOnWriteArrayList(推荐使用这个类):这个类是JUC包下面的一个类,叫写时复制,能够做到读写分离,保证写的线程安全且支持并发读。通过锁+数组拷贝+volatile关键字保证线程安全;CopyOnWriteArrayList在对数组进行操作是时,都有以下四步:加锁、从原数组中拷贝出新数组、在新数组上操作,并把新数组赋给数组容器、解锁。
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
其内部添加和删除共用同一把锁(造成添加删除不能同时进行):



CopyOnWriteArrayList
使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList
时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。CopyOnWriteArrayList
的整个add操作都是在锁的保护下进行的。这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。
当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。

public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}).start();
}
}
//CopyOnWriteArrayList.add()方法源码讲解:
public boolean add(E e) {
// 通过定义一个重入锁进行控制
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 当前list
Object[] elements = getArray();
// 当前list的长度
int len = elements.length;
// 复制一个新的list且在原来的长度+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 把新加的元素添加到新的list上
newElements[len] = e;
// 写完之后重新设置回当前list
setArray(newElements);
return true;
} finally {
// 解锁
lock.unlock();
}
}
CopyOnWriteArrayList的使用场景
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc
或者full gc
2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set
操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList
能做到最终一致性,但是还是没法满足实时性要求;
CopyOnWriteArrayList
合适读多写少的场景,不过这类慎用
因为谁也没法保证CopyOnWriteArrayList
到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
如上面的分析CopyOnWriteArrayList表达的一些思想:
- 1、读写分离,读和写分开
- 2、最终一致性
- 3、使用另外开辟空间的思路,来解决并发冲突
博文参考
JDK11源码