ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)
本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解。
ArrayList源码解析
ArrayList中的变量
通过上图可以看到,ArrayList中总共有7个变量,下面看下每个变量的作用:
/**
* 序列化
*/
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; // non-private to simplify nested class access
/**
* size,是用来记录arrayList中,即elementData里的元素的大小,数组中共有多少个元素
* @serial
*/
private int size;
/**
* 这个参数,是数组所允许的最大长度,要是超出了,可能会报OutOfMemoryError异常
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList构造函数
ArrayList中有三个构造函数:
/**
* 指定一个具有初始容量的空数组,可以自己指定容量的大小
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException 指定的初始容量值若是负值,会抛出这个异常
*/
public ArrayList(int initialCapacity) {
//容量大于0,就把elementData初始化一个具有initialCapacity大小的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//要是指定的容量值是0,就初始化一个空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//指定的容量值小于0.抛出不合法参数异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 无参构造函数,会指定一个初始容量为10的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
*
* @param c 传入的集合,会把集合中的元素放入数组中
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//数组中元素的长度不为0,就把c的元素copy到elementData中
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//要是元素个数是0个,就初始化一个空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList中的add方法
/**
* add方法,会把一个元素添加到数组的末尾
*
* @param e 传入的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//size是目前数组存的元素的个数,传入size+1,即需要的数组长度
//需要的数组长度,是可以再去容纳一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//这里传入的是size+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//这个方法是判断数组是否是一个空数组,要是使用无参构造函数初始化的arrayList,那么返回值就是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//要是elementData是一个空的数组
//判断需要的数组长度和默认容量值哪个大,返回最大的那个
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//要是elementData数组中有元素,直接返回minCapacity
return minCapacity;
}
//这里是,判断数组是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
//记录数组被修改的次数
modCount++;
//要是需要的数组长度大于目前数组的长度,就需要扩容了(即数组的长度是否可以存入下一个元素)
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 扩容方法
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
//旧的数组的长度
int oldCapacity = elementData.length;
//需要扩容的数组的长度,即10*1.5=15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//要是扩容后的数组的长度还是小于需要的最小容量,那么就把需要的最小容量给newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//要是扩容后的数组长度比最大的数组容量还大,就需要控制了
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把扩容之后的数组copy到长度为newCapacity的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//参数小于0.抛异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判断需要的数组容量和数组最大容量哪个大,
//需要的数组容量比数组最大容量还大,就返回int的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
图式:
ArrayList中的add(插入指定位置)方法
先看下 System.arraycopy:
// src 源数组
// srcPos 源数组要复制的起始位置
// dest 要赋值到的目标数组
// destPos 目标数组放置的起始位置
// length 复制的长度
System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length);
继续add方法:
/**
* 将元素插入数组中指定的位置
* @param index 指定的索引值
* @param element 需要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//参数校验,判断要插入的下标是否越界
rangeCheckForAdd(index);
//这个和add(E e)是一样的,判断数组是否需要扩容等
ensureCapacityInternal(size + 1); // Increments modCount!!
//把插入index位置的原有元素以及该元素后面的元素,向右移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//把元素插入到index的位置
elementData[index] = element;
//数组元素的个数加1
size++;
}
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
//要是插入的位置比数组中元素的个数大,或者插入的位置值小于0,就抛出下标越界异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
ArrayList中的get方法
get方法相对而言就比较简单些:
/**
* 根据下标获取指定位置的元素
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
//参数校验,若index比数组中元素的size还大,就抛异常
rangeCheck(index);
//返回对应的元素
return elementData(index);
}
/**
* 在获取数组元素之前,需要进行数据校验
* 若传入的参数不在指定的数组索引范围内,就抛异常
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
ArrayList中的remove(int index)方法
/**
* 移除列表中指定位置的元素,然后把移除元素后面的元素向左移动
*
* @param index 需要移除元素的索引值
* @return 移除的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//参数校验,校验index的值是否在索引所允许的范围内
rangeCheck(index);
//列表的修改次数加一
modCount++;
//先查出对应index位置出的元素,赋值给oldValue
E oldValue = elementData(index);
//把移除的元素后面的所有元素向左移动
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把数组最后一个索引位置数据设置为null
elementData[--size] = null; // clear to let GC do its work
//返回移除的元素数据
return oldValue;
}
ArrayList中的remove(Object o)方法
/**
*
* 从列表中删除指定元素的第一个匹配项
*
* @param o 需要移除的元素
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
//若元素是null,移除第一个匹配为null的元素
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
//若元素不为null,就移除第一个匹配到的元素
} else {
for (int index = 0; index < size; index++)
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) {
//列表修改次数加一
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是线程不安全的,所以不要在遍历中去对ArrayList做修改,否则会出现错误
ArrayList中的clear方法
/**
* 清除列表中的所有元素
*/
public void clear() {
//列表修改次数加一
modCount++;
// clear to let GC do its work 清除所有元素,垃圾回收
//通过遍历把所有元素设置为Null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
CopyOnWriteArrayList源码解析
通过上图可以看到copyOnWrite的实现方式,这种方式适用于读极多,写极少的情况,而且如果数据量巨大,在copy之后的一瞬间,内存占用增加,也会引发问题。CopyOnWriteArrayList是线程安全的。
CopyOnWriteArrayList变量
/** 可重入锁 */
final transient ReentrantLock lock = new ReentrantLock();
/**
* 数组,只能通过getArray和setArray操作
*/
private transient volatile Object[] array;
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
CopyOnWriteArrayList的构造函数
/**
* 创建一个空的列表
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
*
* @param c 传入的集合
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
/**
* 创建包含给定数组副本的列表
*
* @param toCopyIn the array (a copy of this array is used as the
* internal array)
* @throws NullPointerException if the specified array is null
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
CopyOnWriteArrayList的两个add方法
/**
* 添加一个元素到列表的最后面
*
* @param e 需要添加到列表中的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//初始化锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//把elements数组copy到长度为len + 1的newElements数组中,即新的数组长度增加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//然后把元素加到数组的末尾
newElements[len] = e;
//set数组的元素为添加之后的数组
setArray(newElements);
return true;
} finally {
//解锁
lock.unlock();
}
}
/**
* 向指定的索引位置加入元素,加入位置后面的元素需要向右移位
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//初始化一个锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//如果传入的索引值不在数组所允许的范围内,就抛异常
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
//如果插入的索引大小和数组长度一样,那么直接插入到数组末尾
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
//设置新数组的长度比之前的数组长度大一位,即可以插入一个元素
newElements = new Object[len + 1];
//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index之后的元素已经index中的元素copy到index右边,即右移
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//把元素放入到指定的索引处
newElements[index] = element;
//设置数组的引用为新的数组
setArray(newElements);
} finally {
//解锁
lock.unlock();
}
}
向指定索引处插入元素图解:
CopyOnWriteArrayList的两个get方法
get方法比较简单,不做赘述。
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteArrayList的remove方法
/**
* 移除列表中指定索引位置的元素,并把后续的元素向左移动
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//初始化锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//获取索引处原先的旧值
E oldValue = get(elements, index);
int numMoved = len - index - 1;
//如果要移除的是最后一位直接移除
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//新的数组,长度比旧的数组少一位
Object[] newElements = new Object[len - 1];
//同样的,//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index+1之后的元素向左移位
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
//返回移除后的元素
return oldValue;
} finally {
//解锁
lock.unlock();
}
}
在CopyOnWriteArrayList,移除的方法还有另外两个,实现的方法也都大同小异,都是先copy一份列表,然后加锁去操作,移除掉元素,然后再把数组的引用指向移除后的数组即可。
到此,arrayList和CopyOnWriteArrayList源码就结束了,上面的解释以及注释可能有错误或者不足的地方,希望指正,共同进步,多谢!