JDK源码分析系列-ArrayList
Posted laomachu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK源码分析系列-ArrayList相关的知识,希望对你有一定的参考价值。
1、ArrayList本质
数组 + 动态扩容实现的数据列表。
private static final Object[] EMPTY_ELEMENTDATA = {};
// elementData初始为空数组
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
// 指定初始容量,不能为负数
// 如果能预估集合大小,建议初始化时指定容量,避免扩容,提升性能
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
}
private static final int DEFAULT_CAPACITY = 10;
2、主要属性如下
private transient Object[] elementData | ArrayList实际维护的底层数组, 初始为空数组,元素为对象引用。虽然该属性是瞬态,但是ArrayList实现了Serializable接口,并内部自行实现了writeObjec和readObject序列化方法。见下文。 |
private int size | elementData数组中有效元素的个数,并非elementData数组实际大小,扩容后使用null元素占位,即:每次add均+1。size参与元素的按索引检索。 |
3、主要特性
是否允许null元素 | 需要关注下源码中remove方法对null值的处理逻辑,见5 |
是否有序
|
是
|
是否线程安全 | 否 (Vector是线程安全的集合类 或 Collections.synchronizedList包装) |
是否允许重复元素 | 是 |
按索引插入、删除元素的效率低 | 这两类方法均涉及到数组的复制,数组个数越多,执行效率越低 |
顺序插入、检索(get)元素效率高
|
1、顺序插入只有触发扩容时才会进行数组复制,其余场景均连续添加数组元素即可;
2、按索引随机访问数组元素的效率高,数组在内存中连续的内存空间,cpu缓存会读入连续的内存空间,所以数组按下标寻址时都是在cpu缓存中进行,效率较高;
(题外:链表非连续存储,故在内存中寻址,效率不如数组);
|
4、插入元素
// 将指定元素e加入到elementData数组中
public boolean add(E e) {
// 检查是否需要扩容,如果需要则执行扩容操作
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将新元素赋值给底层数组对应的size++的位置
elementData[size++] = e;
return true;
}
// 将指定元素添加至指定索引的位置,
// 1. index位置元素及其后所有有效元素(使用size计算)进行复制并从index+1处进行粘贴;
// 2. 将element设置为index位置的元素;
public void add(int index, E element) {
// 检查index位置是否越界
rangeCheckForAdd(index);
// 检查是否需要扩容,如果需要则执行扩容操作
ensureCapacityInternal(size + 1);
// 完成元素的拷贝移动, 如:
// elementData = [A, B, C, null, null, null, null, null, null, null]
// add(1, D)
// elementData = [A, B, B, C, null, null, null, null, null, null]
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 将新元素赋值给底层数组对应的索引位置 elementData = [A, D, B, C, null, null, null, null, null, null]
elementData[index] = element;
// 有效元素size+1
size++;
}
5、删除元素
// 删除首次匹配到的指定元素
public boolean remove(Object o) {
// 如果删除的元素是null,则不能使用equal判断,故此处进行分支逻辑处理
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
// 使用equals判断对象内容是否相等
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 删除指定索引位置的元素
public E remove(int index) {
// 检查参数索引值是否越界
rangeCheck(index);
// 修改次数自增
modCount++;
// 获取要删除的元素
E oldValue = elementData(index);
// 从index+1处开始复制并在index处进行粘贴
// 如: elementData=[A, B, C, D]
// 删除index=1位置的元素B,调用arrayCopy方法后, elementData=[A, C, D, D]
// 赋值最后一个元素为null,等待gc回收,则 elementData=[A, C, D, null]
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// size自减,将最后一个元素赋值为null,用于gc该多余元素
elementData[--size] = null; // clear to let GC do its work
// 返回被删除元素的引用
return oldValue;
}
6、动态扩容
// 内部动态扩容实现
// minCapacity为最小容量,即容纳有效元素所需的容量,add操作时minCapacity为size + 1/numNew
private void ensureCapacityInternal(int minCapacity) {
// 首次add,底层数据为空数组,则minCapacity为10
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 确认精确的新容量
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果所需的容量大于当前实际容量,则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 当前实际容量
int oldCapacity = elementData.length;
// 计算新容量,实际容量 * 1.5,1.5为时间与空间的权衡,扩容太大,则浪费空间,扩容太小,则发生频繁扩容,则耗费性能
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新的容量比所需的容量小,即:现有容量扩容1.5倍后仍然不够,则使用所需容量进行扩容,当首次添加元素时,新容量为0,所需容量为10,则新容量为10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于最大容量,则触发hugeCapacity操作
// MAX_ARRAY_SIZE为Integer.MAX_VALUE - 8, -8为防止内存溢出,见源码MAX_ARRAY_SIZE常量注释
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 数组复制,根据新容量大小,进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
// java.util.Arrays中的copyOf实现
// 如:original = [0, 1, 2, ...., 9 ] 当前容量oldCapacity=10, 新容量newCapacity=10*1.5=15,则
// 1、创建一个容量为15的新数组copy, elementData = [null, null, null, ...., null]
// 2、将原数组original元素复制到新数组copy中,返回新数组 copy = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, null, null, null]
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 开辟新数组,大小为newCapacity
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 将已有数组original元素复制到新数组copy中
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
/**
* 有些虚拟机数组对象中存在8个字节的对象头,所以此处-8目的为减少OOM的可能性
* 如果超过了MAX_ARRAY_SIZE,那么扩容至Integer.MAX_VALUE
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 最大扩容Integer.MAX_VALUE,即:整数上限值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
7、序列化
虽然ArrayList实现了Serializable接口, 但elementData为瞬态的,作者不希望使用默认的序列化方法对elementData进行序列化。原因为elementData包含占位元素,直接序列化后会导致序列化后的内容比较大,浪费空间及序列化的效率,所以ArrayList中重写writeObjec和readObject方法,writeObjec实现了对elementData中有效元素进行序列化的过程,readObject为反序列化过程。
序列化:ObjectOutputStream.defaultWriteObject序列化非 transient内容 -> ArrayList的writeObject序列化transient的elementData。参考 ObjectOutputStream。
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 behavioural compatibility with clone()
// 序列化实际大小size
s.writeInt(size);
// 按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();
}
}
8、与Vector的比较
Vector的源码实现与ArrayList继承关系一致,底层均基于数组实现且初始默认长度均为10,实现方式基本相同,所以这里不再另起文章进行源码分析,这里对Vector与ArrayList的区别进行梳理:
1、Vector是线程安全,而ArrayList是非线程安全的;
Vector类中的关键方法使用了Synchronized进行修饰,多线程同步执行,保障安全性,自然也牺牲了性能。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2、Vector允许自定义指定扩容增长因子,默认扩容增量100%,ArrayList扩容增量固定为50%;
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
3、Vector的底层数组不是瞬态的且序列化只重写了writeObject方法;
// 未使用transient修饰
protected Object[] elementData;
// 重写writeObject方法与ArrayList不同
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// 获取缓冲中的持久化字段对象
final java.io.ObjectOutputStream.PutField fields = s.putFields();
final Object[] data;
// 同步将capacityIncrement、elementCount添加至持久化字段对象中
//复制底层数组
synchronized (this) {
fields.put("capacityIncrement", capacityIncrement);
fields.put("elementCount", elementCount);
data = elementData.clone();
}
// 将复制后的底层数组添加至持久化字段对象中
fields.put("elementData", data);
// 将持久化字段对象写入流
s.writeFields();
}
4、Vector具有一些特有的方法
public synchronized E firstElement() { ... }
public synchronized E lastElement() { ... }
public synchronized void removeElementAt(int index) { ... }
public synchronized void insertElementAt(E obj, int index) { ... }
public synchronized void addElement(E obj) { ... }
.......
9、迭代器、fail-fast和fail-safe
// 操作计数
protected transient int modCount = 0;
// 获取一个迭代器对象
public Iterator<E> iterator() {
return new Itr();
}
//迭代器类,AbstractList抽象类中的私有内部类,实现了Iterator接口
private class Itr implements Iterator<E> {
// 下一个元素的索引
int cursor = 0;
// 上一个元素的索引,当调用remove方法时重置为-1
int lastRet = -1;
// 迭代器实例化时将期待的modCount值初始化为当前的modCount值,用于快速失败检查
int expectedModCount = modCount;
// 判断是否存在下一个元素
public boolean hasNext() {
// 下一个元素的索引不等于总的底层数组长度,则说明还有下一个元素
return cursor != size();
}
// 获取下一个元素
public E next() {
// 检查底层数组结构是否发生修改,即判断迭代器初始化时备份的expectedModCount的值和当前的modCount是否相等
// 不等,则抛出ConcurrentModificationException
checkForComodification();
try {
// 将当前下一个元素的索引赋值给i
int i = cursor;
// 获取下一个元素
E next = get(i);
// 将当前下一个元素索引作为上一个元素索引
lastRet = i;
// 重置cursor指向新的next索引
cursor = i + 1;
// 返回next对象
return next;
} catch (IndexOutOfBoundsException e) {
// 如果数组越界则优先判断是否发生了并发修改,如果是优先抛出ConcurrentModificationException
checkForComodification();
// 否则 抛出NoSuchElementException
throw new NoSuchElementException();
}
}
// 迭代器对象的删除操作
public void remove() {
// 迭代器对象未吊用过next()方法便调用remove方法的话,会抛出IllegalStateException
if (lastRet < 0)
throw new IllegalStateException();
// 并发修改异常判断
checkForComodification();
try {
// 根据索引删除元素
AbstractList.this.remove(lastRet);
// 下一元素索引减1
if (lastRet < cursor)
cursor--;
//重置上一元素/当前元素索引
lastRet = -1;
// 更新 expectedModCount,所以单线程迭代过程中,通过迭代器对象的remove方法删除元素,不会导致并发修改异常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
// fail-fast检查
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
// 错误方式
List<String> list = new ArrayList<>(3);
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
// 第二次遍历时调用next()方法时,会抛出ConcurrentModificationException
iterator.next();
list.remove(0);
}
// 正确方式
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
// 迭代器对象获取方法
public Iterator<E> iterator() {
// 获取当前数组从索引为0的位置实例化一个迭代器对象
return new COWIterator<E>(getArray(), 0);
}
private static class COWIterator<E> implements ListIterator<E> {
// 迭代器内部对原数组的引用进行了快照(浅拷贝),final修饰,构造函数中初始化,迭代器遍历操作针对该快照完成
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
.....
// 该迭代器与ArrayList不同,不支持remove方法
public void remove() {
throw new UnsupportedOperationException();
}
// 不支持通过迭代器进行set
public void set(E e) {
throw new UnsupportedOperationException();
}
// 不支持通过迭代器进行add
public void add(E e) {
throw new UnsupportedOperationException();
}
}
以上是关于JDK源码分析系列-ArrayList的主要内容,如果未能解决你的问题,请参考以下文章