简单记录一下ArrayList源码阅读
首先看一下ArrayList在Java集合家族中的关系
1.什么是ArrayList?
ArrayList是一种动态数组,可以自动扩展大小来适应需要存储的元素数量。它实现了List接口,可以存储任何类型的数据(包括基本类型和对象类型),并且可以动态改变大小。
ArrayList是基于数组实现的,因此支持快速随机访问元素。它还提供了添加、删除、插入等操作,这些操作的时间复杂度为O(1)或O(n),取决于具体的操作。
除了常规的添加、删除、插入等操作外,ArrayList还提供了一些方便的方法,例如contains()用于判断某个元素是否存在于列表中,indexOf()用于查找某个元素在列表中的索引位置,subList()用于获取指定范围内的子列表等等。
需要注意的是,ArrayList是非线程安全的,如果在多线程环境下使用,需要采取措施进行同步。另外,由于ArrayList是基于数组实现的,因此在频繁进行插入和删除操作时,可能会导致性能下降,建议选择LinkedList等其他数据结构。
下面简单阅读一下ArrayList的初始化方式以及常用的add(),get(),remove()方法
先创建一个demo类,然后我们进去查看源码
public class demo {
public static void main(String[] args) {
// 创建一个ArrayList对象,不带初始容量
List<String> emptyCapacity = new ArrayList<>();
// 创建一个ArrayList对象,传入初始容量
List<String> initCapacity = new ArrayList<>(10);
// 向list中添加元素
emptyCapacity.add("Albert");
emptyCapacity.add("write-down");
initCapacity.add("http");
initCapacity.add("whatname");
initCapacity.add("work");
// 设置对应下标的元素
emptyCapacity.set(0, "Albert");
// 获取对应下标元素
System.out.println(emptyCapacity.get(0));
// 移除对应下标元素
initCapacity.remove(2);
System.out.println(emptyCapacity.size());
System.out.println(emptyCapacity);
System.out.println(initCapacity);
}
}
2.ArrayList的创建方式
2.1.无参创建
/**
存储数组列表元素的数组缓冲区。数组列表的容量是此数组缓冲区的长度。添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展到
DEFAULT_CAPACITY。
*/
transient Object[] elementData;
/**
用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时要 膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
结论 : 从以上代码看出 使用无参构造方法创建ArrayList对象时 是默认初始化为空数组
tip:
为什么要加transient ?
答 : transient关键字是Java语言中的一个修饰符,用于标识某个成员变量不需要进行序列化。在ArrayList中,elementData数组是用来存储列表元素的,在进行序列化操作时,如果不加transient关键字,则会将该数组的内容也一并进行序列化,这样会增加序列化和反序列化的时间开销,并且会占用更多的内存空间。
加上transient关键字后,Java序列化机制就会忽略该成员变量,只对其他非transient的成员变量进行序列化。这样做可以减少序列化和反序列化的时间开销,并且不会占用过多的内存空间。在需要反序列化对象时,ArrayList会使用默认的构造函数创建一个新的elementData数组,然后通过反射机制将反序列化得到的数据填充到该数组中。
因此,在设计可序列化的Java类时,如果某个成员变量不需要进行序列化,应该使用transient关键字进行标识。这样可以避免一些潜在的问题,并提高程序的性能和可维护性。
2.2.初始化容量(有参)
/**
存储数组列表元素的数组缓冲区。数组列表的容量是此数组缓冲区的长度。添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展到
DEFAULT_CAPACITY。
*/
transient Object[] elementData;
/**
用于空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
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 ~ Integer.MAX_VALUE)
- 有效则创建一个大小为初始化数值的对象数组并且赋值给elementData
- 如果初始化数值为0 将用于空实例的共享空数组实例EMPTY_ELEMENTDATA赋值给elementData进行初始化
- 如果初始化的值以上情况都不满足 则会抛出IllegalArgumentException异常
2.3.初始化集合(有参)
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// defend against c.toArray (incorrectly) not returning Object[]
// (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
结论 : 从以上代码看出 初始化大小创建ArrayList对象时
- 将传入的集合通过**toArray()**转为数组
- 判断数组是否为空 为空初始化elementData为EMPTY_ELEMENTDATA 不为空将数组拷贝到elementData
3.添加元素方法Add()
3.1.直接在List后面追加相同类型的元素 add(E e)
/*
这个是列表结构被修改的次数。结构性修改是指改变列表大小或以某种方式使其扰动,以至于正在进行的迭代可能会产 生不正确的结果。
这个字段由迭代器和listIterator方法返回的迭代器和列表迭代器实现使用。如果该字段的值意外地改变,迭代器(或列表迭代器)将在next、remove、previous、set或add操作的响应中抛出ConcurrentModificationException异常。这提供了快速失败的行为,而不是在迭代过程中面对并发修改时的非确定性行为。
子类使用此字段是可选的。如果一个子类希望提供快速失败的迭代器(和列表迭代器),那么它只需要在它的add(int, E)和remove(int)方法(以及任何其他修改列表结构的方法)中增加这个字段。对add(int, E)或remove(int)的单个调用必须不会向此字段添加超过一个,否则迭代器(和列表迭代器)将抛出虚假的ConcurrentModificationExceptions。如果一个实现不希望提供快速失败的迭代器,这个字段可以被忽略。
*/
protected transient int modCount = 0;
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
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(E e)方法时对modCount变量进行标记
- 执行private修饰的add方法
- 调用**grow()**方法进行元素追加操作
- 再调用grow(size + 1)执行Arrays.copyOf(elementData,newCapacity(minCapacity));
- 通过newCapacity(minCapacity)在原数组容量的基础上扩容1.5倍
- 扩容成功后执行**elementData[s] = e;size = s + 1;**将数据添加到扩容后的末尾
3.2.在指定索引位置添加元素 add(int index, E element)
private String outOfBoundsMsg(int index) {
return "Index: "+ index +", Size: "+ size;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//在指定索引位置添加元素
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
结论 :
- 在插入函数之前,先调用rangeCheckForAdd()方法进行索引检查 不合法则抛出IndexOutOfBoundsException(索引越界异常)
- 对modCount变量进行标记 定义一个记录未插入元素前的数组长度变量s (使用final修饰防止并发修改造成数组异常) 定义elementData数组保存未插入元素前 数组的元素
- 如果**(s = size) == (elementData = this.elementData).length条件成立 执行grow()**方法进行数组扩容(在上面已经介绍了如何实现的了)
- 扩容成功后 调用**System.arraycopy()**方法进行数组拷贝的赋值
- 步骤4完成后将数值覆盖在对应的位置上 然后进行长度记录加1
tip:
**System.arraycopy()**方法,它是一个用于数组复制的方法。该方法可以将一个数组中的一部分或整个内容复制到另一个数组中,实现数组之间的数据拷贝。下面是该方法的语法:
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
其中,参数解释如下:
src
:源数组,即要复制的数组。srcPos
:源数组中的起始位置,从该位置开始复制元素。dest
:目标数组,将复制的元素放入此数组。destPos
:目标数组中的起始位置,从该位置开始粘贴元素。length
:要复制的元素数量。
使用该方法时,需要确保目标数组的长度足够容纳复制的元素,并且在复制过程中不会出现索引越界的错误。此外,System.arraycopy()
方法只能复制数组类型的数据,不能用于其他类型的对象。
**System.arraycopy()**方法是一个底层的数组复制方法,它使用了系统级别的内存操作来实现高效的数组拷贝。该方法的具体原理如下:
- 首先,该方法会检查传入参数的合法性,包括源数组和目标数组是否为null,以及位置参数是否超出数组边界。
- 之后,该方法会根据传入的源数组、源位置、目标数组、目标位置和要复制的长度进行计算,确定需要复制的元素数量。
- 接下来,该方法会通过内存拷贝的方式,将源数组中指定的元素复制到目标数组中。这个过程不涉及元素的类型转换或其他额外的操作,直接在内存层面进行数据的复制。
- 最后,该方法完成数组拷贝后,返回结果,不会改变源数组和目标数组的引用。
需要注意的是,System.arraycopy()方法是一个低级别、底层的数组复制方法,并且它是非同步的。因此,在多线程环境下使用时需要注意同步问题。
3.3 addAll(Collection<? extends E> c) 在原来的列表基础上追加对应元素的集合数据
public boolean addAll(Collection<? extends E> c) {
//将集合转为Object数据
Object[] a = c.toArray();
//修改数+1
modCount++;
//获取追加元素的长度
int numNew = a.length;
//如果为0 表示添加了空数据 直接返回 false添加失败 否则就继续往下执行追加操作
if (numNew == 0)
return false;
//定义一个空的数组
Object[] elementData;
final int s;
//此处是为了判断追加的元素容量是否超过了当前列表数据缓存容量的大小 如果超过就执行扩容操作 不超过 就执行下面的底层数据扩容代码
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
3.4 addAll(int index, Collection<? extends E> c)在指定索引处追加元素
public boolean addAll(int index, Collection<? extends E> c) {
//先对追加的索引进行合法校验
rangeCheckForAdd(index);
//将集合转为Object数据
Object[] a = c.toArray();
//修改数+1
modCount++;
//获取追加元素的长度
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
//此处是为了判断追加的元素容量是否超过了当前列表数据缓存容量的大小 如果超过就执行扩容操作 不超过 就执行下面的底层数据扩容代码
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
//此处是为了判断要追加的位置,大于0则在0~size之间追加 小于等于0在size后面添加
int numMoved = s - index;
if (numMoved > 0)
System.arraycopy(elementData, index,
elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size = s + numNew;
return true;
}
4.获取元素方法get(int index)
public E get(int index) {
//这里调用Objects工具类的checkIndex(int index,int size)方法
//用于检查执行操作的index的合法性
Objects.checkIndex(index, size);
//合法 调用方法返回对应下标的值
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
5.设置元素方法set(int index,E element)
public E set(int index, E element) {
//这里调用Objects工具类的checkIndex(int index,int size)方法
//用于检查执行操作的index的合法性
Objects.checkIndex(index, size);
//获取未修改前的值
E oldValue = elementData(index);
//赋值
elementData[index] = element;
//赋值成功 返回旧值
return oldValue;
}
6.删除方法remove()、clear()方法
6.1 remove(int index) 移除对应下标的值
public E remove(int index) {
//这里调用Objects工具类的checkIndex(int index,int size)方法
//用于检查执行操作的index的合法性
Objects.checkIndex(index, size);
//复制当前数组
final Object[] es = elementData;
//获取旧值
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
//调用方法
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
//修改数+1
modCount++;
final int newSize;
//判断移除对应数据后 index是否是最后一个 是直接设值为null 不是则开始移动元素填补空位
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
6.2 remove(Object o) 移除对应值的元素
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
// 遍历,移除对应元素
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
6.3 removeAll(Collection<?> c) 移除对应集合元素
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false, 0, size);
}
//批量移除
boolean batchRemove(Collection<?> c, boolean complement,
final int from, final int end) {
//判断是否是null 是抛出空指针异常
Objects.requireNonNull(c);
final Object[] es = elementData;
int r;
// Optimize for initial run of survivors
for (r = from;; r++) {
//如果传入集合长度为0 直接返回操作失败false
if (r == end)
return false;
//查找传入要删除的数据中是否有不存在的数据
if (c.contains(es[r]) != complement)
break;
}
// w是元素个数
int w = r++;
try {
for (Object e; r < end; r++)
//判断c中是否存在es集合中不存在的元素 是则复制
if (c.contains(e = es[r]) == complement)
es[w++] = e;
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//出现错误就执行回退操作
System.arraycopy(es, r, es, w, end - r);
w += end - r;
throw ex;
} finally {
modCount += end - w;
shiftTailOverGap(es, w, end);
}
return true;
}
private void shiftTailOverGap(Object[] es, int lo, int hi) {
System.arraycopy(es, hi, es, lo, size - hi);
//将对应的元素设为null值 相当于清除操作了
for (int to = size, i = (size -= hi - lo); i < to; i++)
es[i] = null;
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
6.4 clear() 清空方法
public void clear() {
modCount++;
final Object[] es = elementData;
//将所有数据清空
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
END
(这是本人第一次解读ArrayList源码 可能有错漏的地方 如果有发现请指正 我会及时修改的 希望能一起进步 感谢感谢)