概述

ArrayList 是基于数组实现的,是一个能自动扩展的动态数组。

ArrayList 是线程不安全的,多线程情况下添加元素会出现数组越界的情况,而且数组赋值操作不是原子操作,会导致多线程情况下数据混乱。

ArrayList 实现了 Serializable 接口,支持序列化;

实现了 Cloneable 接口,支持克隆;

实现了 RandomAccess 接口,支持通过下标进行快速随机访问;

ArrayList 默认大小为 10 ,扩容基数为 50%,而扩容的代价是高昂的,可以通过手动调用 ensureCapacity 方法实现扩容,以减少自动扩容次数。

ArrayList 通过采用 Fail-Fast 快速失败机制,面对并发修改时,迭代器可以通过抛出 ConcurrentModificationException 异常快速失败,而不会在某个未知的时间发生不确定的行为。

类属性

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
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 = {}; /**
ArrayList 的元素存储在其中的数组缓冲区。
ArrayList 的容量就是这个数组缓冲区的长度。
添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 DEFAULT_CAPACITY。
*/
transient Object[] elementData; // non-private to simplify nested class access /**
* ArrayList的大小(它包含的元素数)。
*
* @serial
*/
private int size; /**
*要分配的最大数组大小。
*有些虚拟机在数组中保留一些头字。
*尝试分配较大的阵列可能会导致
*OutOfMemoryError:请求的数组大小超过VM限制
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /**
此列表在结构上被修改的次数。迭代器遍历列表时快速失败的参考字段
*/
protected transient int modCount = 0; }

ArrayList

构造函数

无参构造函数

  ArrayList 中存储的数据在 elementData 中,使用无参构造函数创建 ArrayList 对象时,elementData 会被初始化为一个空数组,当添加数据时会对 elementData 进行扩容为默认大小为 10 的数组。

/**
* 构造一个初始容量为 10 的空列表。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

ArrayList()

自定义容量构造函数

  根据指定的容量大小构造一个 ArrayList 对象。

/**
* 构造一个具有指定初始容量的空列表。
*/
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(int)

列表转 ArrayList

把其他 Collection 列表转为 ArrayList 集合。(不常用)

/**
* 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray(); // Collection 列表转数组
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a; // 如果是 ArrayList 则直接赋值
} else {
// 其他类型则转为 Object 数组再赋值
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// Collection 列表为空则直接赋值为空数组
elementData = EMPTY_ELEMENTDATA;
}
}

ArrayList(Collection<? extends E>)

核心方法

add 方法

add(E):boolean

最常用的 add 方法,将元素追加到列表末尾。

/**
* 将指定的元素附加到此列表的末尾。
*/
public boolean add(E e) {
// 列表扩容、操作数(modCount)修改
ensureCapacityInternal(size + 1); // Increments modCount!! // 添加元素到指定位置
// 这不是一个原子操作,Size++ 操作会返回自增前的 Size 值
elementData[size++] = e;
return true;
}

add(E):boolean

add(int, E):void

在指定位置插入元素(不常用)

/**
在此列表中的指定位置插入指定元素。
将当前在该位置的元素(如果有)和任何后续元素向右移动(向它们的索引添加一个)。
*/
public void add(int index, E element) {
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

add(int, E):void

addAll(Collection):boolean

将列表元素添加到 ArrayList 末尾(不常用)

/**
按照指定集合的​​迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾。
如果在操作进行时修改了指定的集合,则此操作的行为未定义。
这意味着如果指定的集合是这个列表,并且这个列表是非空的,那么这个调用的行为是未定义的。)
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

addAll(Collection<? extends E>):boolean

addAll(int index, Collection):boolean

添加列表元素到指定位置(不常用)

/**
从指定位置开始,将指定集合中的所有元素插入此列表。
将当前在该位置的元素(如果有)和任何后续元素向右移动(增加它们的索引)。
新元素将按照指定集合的​​迭代器返回的顺序出现在列表中。
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

addAll(int index, Collection<? extends E>):boolean

add(E) 方法的具体实现

每次添加元素前,都会判断数组是否需要扩容。之后才会执行代码

elementData[size++] = e;
将元素添加到数组末尾。在多线程添加元素的场景下,这种添加方式是不安全的,会抛出数组越界的错误。因为 size++ 不是一个原子操作,它是自增后,再返回自增前的值,这是两个步骤。
在判断是否需要扩容时,有以下几种情况:
1.首次添加元素时
    • 数组会被判断为空数组,这时会被扩容为默认大小 10。
    • 如果使用了自定义大小的构造函数,那么会判断自定义的容量大小和默认容量大小,如果小于默认容量大小,还是会进行扩容到默认大小。
2. 非首次添加元素,会判断当前数组大小和最小期望容量大小,小于最小期望容量就会进入扩容的流程,执行 grow(int) 方法。在这个方法中,会把新容量值设置为原来容量的 1.5 倍,再判断新容量是否足够。直到分配合理的扩容值,否则无法分配时将会抛出 OOM 异常。分配容量后调用 Arrays.copy(Object[], int) 方法,这个方法会根据新容量值创建新数组,然后调用 System.arraycopy(Object, int, Object, int, int) 方法复制旧数组的元素到新数组上。
至此就完成了扩容操作。
整个 ArrayList 添加元素的流程就是:判断是否需要扩容 ?扩容 : 不扩容 -> 新元素添加到数组末尾。
具体的代码分析如下:

/**
minCapacity 是当前数组实际存储元素数量 +1,也就是执行本次添加元素操作,需要的最小数组容量大小
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} /**
判断是否需要扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
// 记录操作数
modCount++; // 如果当前需要的最小数组容量大小 > 当前数组容量,那么就需要对数组容量重新调整
// 也就是数组已经添加不了下一个元素了,需要要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} /**
返回合适的初始化容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断是否为空数组,如果是空的,说明是首次添加元素,此时数组还没有初始化,没有容量
// 那么就需要判断期望的最小容量(minCapacity)与默认容量 10 的大小,返回最大值
// 由于这个方法被 ensureCapacity(int) 调用了,所以需要判断大小,如果是未被调用公开,就直接返回 10 好了
// ensureCapacity(int) 可以手动调整数组容量,以减少 ArrayList 自动扩容的次数
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} /**
ArrayList 扩容的核心方法
*/
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; // 如果新的容量值大于数组最大容量值(int 类型最大值 - 8)2^31 - 1 - 8
// 那么需要对新容量设置合理的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 把旧数组拷贝到新容量的数组,大于原来长度的填充 null
elementData = Arrays.copyOf(elementData, newCapacity);
} /**
返回数组扩容容量最大值
*/
private static int hugeCapacity(int minCapacity) {
// 如果最小期望容量为负数,说明最小期望容量已经超过 int 类型最大值(2^31 - 1)
// int 类型(范围-2^32 ~ 2^31-1)表示的 2^31 是负数,
// 以二进制表示,其实int的数值范围是一个圈,超出最大值变成负数,低于最小值变成正数
// 相关知识需要了解二级制、补码
// 所以这里如果为负数,说明最小期望容量溢出,会抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError(); // 如果不为负数,那么最小期望容量在数组最大容量(2^31 - 1 - 8)和 int 类型最大值(2^31 - 1)之间
// 选择一个合理的最大值
// MAX_ARRAY_SIZE 为 int 类型最大值 -8 是因为某些 JVM 在数组中保留一些头字,
// 超过这个值可能会抛出 OOM
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

ArrayList 扩容核心代码

remove 方法

ArrayList 的删除方法有以下几个:

remove(int):E

remove(Object):boolean

removeAll(Collection):boolean

removeIf(Predicate):boolean

removeRange(int,int):void

这里只分析常用的几个方法。

remove(int):E

根据索引删除指定元素,利用 System.arraycopy 方法采取复制-覆盖-置空的方式删除元素。其具体原理在源码中有注释分析。

源码分析

/**
移除此列表中指定位置的元素。 将任何后续元素向左移动(从它们的索引中减去一个)。
*/
public E remove(int index) { // 检查索引是否越界
rangeCheck(index); // 数组操作次数 +1
modCount++; // 需要删除的元素赋给临时变量
E oldValue = elementData(index); // 需要移动的元素个数
// 举个栗子:数组长度为 10 ,其中有 6 个元素,现在删除第 3 个元素(下标为 2),
// 那么 nmMoved = 6 - 2 - 1 = 3 需要移动两个元素
// 如果要删除第 8 个元素(下标为 9),第 8 个元素肯定是不存在的
// 计算 numMoved = 6 - 9 - 1 = -4 移动 -4 个元素?显然不合理
// 所以 numMoved > 0 时才需要移动
// 为 0 或者 < 0 只需要把最后一个元素重置为 null 就可以了
int numMoved = size - index - 1;
if (numMoved > 0)
// 这里使用 System.arraycopy(Object, int, Object, int, int) 拷贝数组元素到原数组,
// 相当于需要移动的元素 覆盖 原来的元素
// System.arraycopy(原数组, 原数组拷贝起始位置,目标数组,目标数组拷贝起始位置,拷贝长度(这个长度包括起始位置))
// 这句拷贝代码就是从原数组 删除索引+1 的位置 拷贝两个元素 到 删除索引 的位置,
// 那么数组最后的元素就是无效的了,后面那句代码就是把无效元素重置为 null 了
// 从而达到类似元素移动的效果。
System.arraycopy(elementData, index+1, elementData, index,
numMoved); // 把最后的元素重置为 null,
// 那么此时这个元素将不再被数组对象引用,下次 GC 时这个对象将可能被当作垃圾清除
elementData[--size] = null; // clear to let GC do its work // 返回需要删除的索引对应的值
return oldValue;
} /**
检查给定的索引是否在范围内
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

remove(int):E

图解分析

remove(Object):boolean

匹配指定元素并删除,但是匹配的自定义对象 Object 需要实现 equals 方法,具体在代码分析中有体现。

源码分析

/**
从此列表中删除第一次出现的指定元素(如果存在)。
如果列表不包含该元素,则它保持不变。
更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果这样的元素存在)。
如果此列表包含指定的元素(或等效地,如果此列表因调用而更改),则返回true 。
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
// 找到第一个 null 元素
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
// 匹配第一个指定的元素
// 自定义对象需要重写 equals 方法,否则默认调用超类 Object 的 equals 方法
// Object 的 equals 判断的是对象的内存地址
// 对象 o 和 elementData[index] 的内存地址肯定是不同的
// 所以需要重写 equals
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
} /**
跳过边界检查并且不返回移除的值的私有移除方法。
这个方法和 remove(int) 的实现是相同的,只不过是私有的且没有返回值
*/
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
}

remove(Object):boolean

removeAll(Collection):boolean

批量删除数组中未匹配的元素,参数 Collection 是元素对照列表,在对照列表中存在的元素将被清除。

需要注意的是,如果数组中存在与对照列表类型不一致或其它情况导致匹配出现异常的,匹配将会终止,且未匹配的元素将被保留在原数组。具体在代码分析中体现

源码分析

/**
从此列表中删除包含在指定集合中的所有元素。
*/
public boolean removeAll(Collection<?> c) {
// 检查对象不为 null
Objects.requireNonNull(c);
return batchRemove(c, false);
} /**
批量删除匹配列表之中或者之外的数组中的元素
complement 是否在列表之中
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0; // r 为 数组实际元素大小, w 为包含/不包含的元素个数
boolean modified = false;
try {
for (; r < size; r++)
// 符合 contains == true/false 的元素被移动到临时数组的左端
// 同时更新 w 的值
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 如果上面的 contains 抛出异常,将在这里进行处理
// size - r 是上面循环 contains 抛出异常时尚未匹配的元素个数
// 把这部分尚未匹配的元素追加到 w 后,也就是已经匹配了(包含/不包含在 c 中)的元素
// 然后把 w 加上尚未匹配的元素个数
// 此时 w = 匹配了(包含/不包含在 c 中)的元素个数 + 抛异常尚未匹配的元素个数
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
} // 此时数组中的元素就是 w 的那些元素 + 尚未匹配的元素 + 未被移动的元素(w 中的元素和尚未匹配的元素都是向左移动了的)
// 这里清理的就是那些未移动的元素,让 GC 可以回收
// 同时修改数组实际元素个数为 w
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}

removeAll(Collection<?>):boolean

图解分析

retainAll(Collection):boolean

这是 removeAll(Collection) 方法的逆方法,包含在 Collection 对照列表的将被保留,未包含的将被删除,其实现方式与 removeAll(Collection) 方法完全一样。

/**
保留列表中包含的元素
*/
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}

retainAll(Collection<?>):boolean

图解分析

set 方法

用指定元素替换索引位置的元素

源码分析

/**
用指定的元素替换此列表中指定位置的元素。
*/
public E set(int index, E element) {
// 检查索引越界
rangeCheck(index); // 获取索引位置元素
E oldValue = elementData(index); // 替换索引位置元素
elementData[index] = element; // 返回原来的对象
return oldValue;
}

set 方法

get 方法

获取索引位置元素

源码分析

/**
返回列表中指定位置的元素。
*/
public E get(int index) {
// 检查索引越界
rangeCheck(index); // 获取索引位置元素
return elementData(index);
} // 强制类型转换
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

get 方法

copy 方法

重写超类 Object 的 copy 方法,浅拷贝一个 ArrayList 实例

/**
返回该ArrayList实例的浅副本。(元素本身不会被复制。) 浅拷贝
*/
public Object clone() {
try {
// 调用超类 Object 的 clone() 方法,创建一个 ArrayList 实例
ArrayList<?> v = (ArrayList<?>) super.clone(); // 拷贝当前数组到新实例 ArrayList
// 内部使用反射创建了一个长度为 size 的数组对象
// 使用 System.arraycopy 拷贝原数组元素到新数组
// System.arraycopy 是浅拷贝。
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);
}
}

copy 方法

indexOf(Object):int

/**
通过 for 循环全列表扫描,效率及其低下,可以换成 Collections 工具类的 binarySearch 方法
返回此列表中指定元素第一次出现的索引,如果此列表不包含该元素,则返回 -1。
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

indexOf(Object):int

RandomAccess 接口

RandomAccess 是一个标记接口。Conllections 工具类的 binarySearch 方法,可以根据该标记接口选择遍历方式。具体在代码分析中体现。

/**
List实现使用的标记接口来指示它们支持快速(通常是恒定时间)随机访问。
*/
public interface RandomAccess {
}

RandomAccess

binarySearch 源码分析

/**
使用二进制搜索算法搜索指定对象的指定列表。
在进行此调用之前,必须根据其元素的自然顺序(如sort(List)方法)将列表按升序sort(List) 。
如果未排序,则结果未定义。 如果列表包含多个与指定对象相等的元素,则无法保证会找到哪一个。
此方法在 log(n) 时间内运行,用于“随机访问”列表(提供近乎恒定时间的位置访问)。
如果指定的列表未实现RandomAccess接口并且很大,则此方法将执行基于迭代器的二进制搜索,执行 O(n) 链接遍历和 O(log n) 元素比较。
*/
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
// 列表继承了 RandomAccess 接口或者列表元素小于 5000,使用基于索引的二分法查找
// 否则使用迭代器的二分法查找
// 比如 LinkedList 这种链表式列表,没有继承 RandomAccess 接口,使用迭代器查找效率更高
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
} /**
基于索引二分法查找
*/
private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1; while (low <= high) {
int mid = (low + high) >>> 1; // 根据索引获取元素
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key); if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
} /**
基于迭代器的二分法查找
*/
private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1; // 获取列表的迭代器对象
// 这里会返回一个 ListItr 对象,就是一个迭代器 ListIterator<? extends Comparable<? super T>> i = list.listIterator(); while (low <= high) {
int mid = (low + high) >>> 1; /**
从迭代器遍历查找对象
*/
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key); if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
} /**
通过重新定位指定的列表 listIterator 从给定列表中获取第 i 个元素。
*/
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null;
// 每次迭代查找都会根据 next 计算 nextIndex 的值
// 这在超大列表查找时,不必每次都从列表开头查找,
// 而是根据上次查找的末尾索引计算下次开始查找的开始索引 nextIndex
// 所以迭代器查找超大列表或链式列表时效率更高
int pos = i.nextIndex();
if (pos <= index) {
do {
obj = i.next();
} while (pos++ < index);
} else {
do {
obj = i.previous();
} while (--pos > index);
}
return obj;
}

binarySearch

Fail-Fast 快速失败机制

ArrayList 的私有内部类 ListItr ,是 ArrayList 的迭代器实现类。

线程使用 ArrayList 的迭代器时,都会实例化一个 Itr 的私有内部类,

Itr 实现了 Iterator 接口,ListItr 继承了 Itr 类并实现接口 ListIterator 接口,继承关系如下:

 

实例化 Itr 类时,会使用当前 ArrayList 的 modCount 属性给 expectedModCount 初始化。

每次使用迭代器的 next、previous、remove、add、set等方法时都会调用 checkForComodification 方法

判断 Arraylist 对象的 modCount 值与 Itr 迭代器对象的 expectedModCount 值比较是否一致,

如果不相等,则为避免迭代产生不正确的结果,会立即抛出 ConcurrentModificationException 异常。

这就是为什么 ArrayList 的所有与列表操作有关的方法都会加上 modCount++ 这句代码,

目的就是一旦检测到列表发生改变就能立即失败,哪怕是在多线程环境下,这个机制依旧是有效的。

这就是 ArrayList 所谓的快速失败机制。

checkForComodification 方法源码

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

checkForComodification

源码分析回顾

ArrayList 是一个基于数组实现的可动态扩展的集合容器。它不是线程安全的。它的基本操作(CRUD)还是比较简单的,对于 ArrayList 的源码分析提炼出以下值得借鉴的点:

1. 它的 add、remove 以及扩容方法核心都是基于数组的复制、覆盖、重置等实现的。比如 fastRemove 方法就是很好的示例

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
}

2. Fail-Fast 快速失败机制,就是基于操作标识字段 modCount 实现的,每个线程通过迭代器遍历 ArrayList 都会实例化一个迭代器实例,所以在多线程情景下,多个迭代遍历不会影响彼此,可一旦 modCount 被某个线程修改,那么所有线程的遍历都会快速失败。

关于快速失败机制,核心就是 modCount 标志字段和工厂模式下的 Itr 迭代器实例。所以可以借鉴在工厂模式创造的对象中加入标识字段,这样就可以通过字段值的变化监控所有的实例。

3. ArrayList 实现的 RandomAccess 接口,提供的快速随机访问的能力,其实质就是使用二分法(binarySearch)取代全列表遍历(indexOf),那么在列表查找方面可以借鉴此实现。

ArrayList 模拟代码(手写代码)

package sx.project.cases;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.RandomAccess; /**
<p style="color:rgb(0,255,0);">ArrayList 的自定义实现</p>
ArrayList 是一个基于数组的可扩展的动态数组,所以需要实现以下功能<br/>
自动扩展、add、remove、set、get<br/>
继承 AbstractList 是为了实现 List 的装饰模式实现类
实现 RandomAccess 让 MyList 支持快速随机访问的能力
实现 Cloneable 让 MyList 实现拷贝
实现 Serializable 让 MyList 可以序列化和反序列化
**/
public class MyList<T> extends AbstractList<T> implements Serializable, RandomAccess, Cloneable { private static final long serialVersionUID = 169823782352696015L; /**
* <p style="color:rgb(127,255,212);">数组默认大小</p>
*/
private static final int DEFAULT_CAPACITY = 10; /**
* <p style="color:rgb(127,255,212);">数组最大容量/p>
*/
private static final int MAX_CAPACITY = Integer.MAX_VALUE - 8; /**
* <p style="color:rgb(127,255,212);">数组</p>
*/
private T[] array; /**
* <p style="color:rgb(127,255,212);">数组元素数量</p>
*/
private int size; /**
* <p style="color:rgb(127,255,212);">数组最小期望容量</p>
*/
private final int min_capacity = size + 1; /**
* <p style="color:rgb(127,255,212);">创建一个指定容量的 MyList</p>
*/
@SuppressWarnings(value = "unchecked")
public MyList (int initialCapacity) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Capacity " + initialCapacity);
} else if (initialCapacity == 0) {
array = (T[]) new Object[DEFAULT_CAPACITY];
} else {
if (initialCapacity < DEFAULT_CAPACITY) {
array = (T[]) new Object[DEFAULT_CAPACITY];
} else {
array = (T[]) new Object[initialCapacity];
}
}
} /**
* <p style="color:rgb(127,255,212);">创建一个默认容量的 MyList</p>
*/
public MyList () {
this(DEFAULT_CAPACITY);
} /**
* <p style="color:rgb(255,165,0);">在列表末尾添加元素</p>
* @param element 添加的元素
* @return 是否添加成功
*/
public boolean add(T element) {
add(size, element);
return true;
} /**
* <p style="color:rgb(255,165,0);">在列表指定位置添加元素</p>
* @param index 指定位置下标
* @param element 添加的元素
*/
public void add(int index, T element) {
// 判断是否越界
rangeCheck(index);
// 判断是否需要扩容
capacityInternal();
// 拷贝数组
System.arraycopy(array, index, array, index + 1, size - index);
// 新元素插入指定位置
this.array[index] = element;
// 数组实际容量 +1
size++;
} /**
* <p style="color:rgb(255,165,0);">删除指定位置的元素</p>
* @param index 指定位置的下标
* @return 删除前的元素
*/
public T remove(int index) {
rangeCheck(index);
T oldElement = array[index];
int moveNum = size - index - 1;
if (moveNum > 0) {
System.arraycopy(array, index + 1, array, index, moveNum);
}
array[--size] = null;
return oldElement;
} /**
* <p style="color:rgb(255,165,0);">删除第一个匹配的元素</p>
* @param element 被删除的元素
* @return 是否删除
*/
public boolean remove(Object element) {
if (element == null) {
for (int index = 0; index < size; index++) {
if (array[index] == null) {
remove(index);
return true;
}
}
} else {
for (int index = 0; index < size; index++) {
if (array[index].equals(element)) {
remove(index);
return true;
}
}
}
return false;
} /**
* <p style="color:rgb(255,165,0);">替换指定位置的元素</p>
* @param index 指定位置的下标
* @param element 替换的元素
* @return 替换前的元素
*/
public T set(int index, T element) {
rangeCheck(index);
T oldElement = array[index];
array[index] = element;
return oldElement;
} /**
* <p style="color:rgb(255,165,0);">获取指定位置的元素</p>
* @param index 指定位置的下标
* @return 获取的元素
*/
public T get(int index) {
rangeCheck(index);
return array[index];
} /**
* <p style="color:rgb(255,165,0);">拷贝当前的 MyList</p>
* @return 拷贝对象
*/
@SuppressWarnings(value = "unchecked")
@Override
protected Object clone () throws CloneNotSupportedException {
try {
MyList<T> list = (MyList<T>) super.clone();
T[] array = (T[]) new Object[size];
System.arraycopy(this.array, 0, array, 0, size);
list.array = array;
return list;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
} @Override
public String toString () {
StringBuilder sb = new StringBuilder("MyList[");
for (int index = 0; index < size; index++) {
sb.append(array[index] == null ? "null" : array[index].toString());
if (index < size - 1) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
} /**
* <p style="color:rgb(127,255,212);">清除所有元素,以便 GC 可以回收</p>
*/
public void clear() {
for (int index = 0; index < size; index++) {
array[index] = null;
}
size = 0;
} /**
* <p style="color:rgb(127,255,212);">获取当前数组实际大小</p>
*/
public int size() {
return size;
} /**
* <p style="color:rgb(127,255,212);">判断 MyList 是否为空</p>
*/
public boolean isEmpty() {
return this.size == 0;
} /**
* <p style="color:rgb(255,165,0);">判断是否需要扩容</p>
*/
private void capacityInternal(){
if (size + 1 > array.length) {
resize();
}
} /**
* <p style="color:rgb(255,165,0);">扩容核心</p>
*/
@SuppressWarnings(value = "unchecked")
private void resize() {
int oldCapacity = array.length; // 新容量为原始容量的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量小于最小期望容量,则重新赋值
// 大于最大容量,则赋值最大容量
// 新容量小于 0,此时将抛出内存溢出的错误
if (newCapacity < 0) {
throw new OutOfMemoryError();
}
if (newCapacity < min_capacity) {
newCapacity = min_capacity;
} else if (newCapacity > MAX_CAPACITY) {
newCapacity = MAX_CAPACITY;
}
// 创建新容量数组
T[] newArray = (T[]) new Object[newCapacity];
// 数组元素全量拷贝
System.arraycopy(array, 0, newArray, 0, size);
// 指向新数组
array = newArray;
} /**
* <p style="color:rgb(127,255,212);">检查索引是否越界</p>
*/
private void rangeCheck (int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException(outOfBoundMsg(index));
}
} private String outOfBoundMsg(int index){
return "Index: "+index+", Size: "+size;
} }

手写代码

ArrayList 源码分析和自定义ArrayList实现的更多相关文章

  1. 面试必会之ArrayList源码分析&手写ArrayList

    简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于对元素进行查找,效率非常高. 线程 ...

  2. 读源码从简单的集合类之ArrayList源码分析。正确认识ArrayList

    一.查看源码的方法 1.看继承结构 看这个类的层次结构,处于一个什么位置,可以在自己心里有个大概的了解. 我是有idea查看的, eg:第一步: 第二步: 第三步:查看子类或者继承关系:F4 2.看构 ...

  3. ArrayList源码分析超详细(转载)

    ArrayList源码分析超详细   ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要 ...

  4. Java集合干货——ArrayList源码分析

    ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...

  5. ArrayList 源码分析

    ArrayList 源码分析 1. 结构   首先我们需要对 ArrayList 有一个大致的了解就从结构来看看吧. 1. 继承   该类继承自 AbstractList 这个比较好说 2. 实现 这 ...

  6. ArrayList源码分析超详细

    ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要分析的类(ztrl+N查找ArraLi ...

  7. Java - ArrayList源码分析

    java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 nul ...

  8. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  9. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

随机推荐

  1. turtle color设置的几种方式

    t.colormode() 查看色彩模式,缺省1.0,即RGB范围在0-1 模式切换:参数填1.0或255 t.colormode(1.0) t.colormode(255) 设置颜色,以设置penc ...

  2. P4351-[CERC2015]Frightful Formula【组合数学,MTT】

    正题 题目链接:https://www.luogu.com.cn/problem/P4351 题目大意 \(n*n\)的矩形,给出第一行和第一列的数,剩下的满足\(F_{i,j}=a*F_{i,j-1 ...

  3. P3293-[SCOI2016]美味【主席树】

    正题 题目链接:https://www.luogu.com.cn/problem/P3293 题目大意 给出一个长度为\(n\)的序列,\(m\)次询问给出\(b,x,l,r\)表示询问在\([l,r ...

  4. Java(一)——基础知识

    引言 之前一直对 Java 怀有固执的偏见,以为 Java 是编写前端的语言,作为一个机械生,非常抗拒去学它. 但是最近接触一点以后,完全改观了先前的看法,于是开启了对 Java 的大学习. 一.数据 ...

  5. DBeaver MSSQL 支持TLS设置

    DBeaver是一个基于 Java 开发,免费开源的通用数据库管理和开发工具,使用非常友好的 ASL 协议.可以通过官方网站或者 Github 进行下载. 由于 DBeaver 基于 Java 开发, ...

  6. 阿里:MySQL数据库规范

    阿里:MySQL数据库规范 简介:基于阿里数据库设计规范扩展而来 设计规范 1.[推荐]字段允许适当冗余,以提高查询性能,但必须考虑数据一致.冗余字段应遵循: 不是频繁修改的字段. 不是 varcha ...

  7. List接口常用实现类对比

    相同点 都实现了List接口 储存了有序 可重复的数据 不同点 ArrayList 线程不安全 但是效率高 底层使用 Object[] elementData 实现 LinkedList 底层使用双向 ...

  8. hd-cg辉度通用代码生成器

    HD-CG 辉度通用代码生成器 主要特点: 1. 自定义代码模板:通过简单的默认变量自行编写代码模板,如果默认变量不满足需求,也可增加自定义变量. 2. 自定义数据源:可自定义添加多个项目的数据库,数 ...

  9. pandas 取 groupby 后每个分组的前 N 行

    原始数据如下: (图是从 excel 截的,最左1行不是数据,是 excel 自带的行号,为了方便说明截进来的) 除去首行是标题外,有效数据为 28行 x 4列 目前的需求是根据 partition ...

  10. Alpha阶段初始任务分配

    项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 团队项目-计划-Alpha阶段说明书 一.Alpha阶段总体规划 进行服务器相关部署 进行开发相关技术学习 ...