ArrayList
ArrayList的底层原理:
ArrayList的底层是根据数组实现的,ArrayList是线程不安全的,适用于对元素进行查找操作,效率非常高。随机查找的性能要高于LinkedList,但是对于插入和删除元素的性能要低于LinkedList。
ArrayList中的变量:
- serialVersionUID:
Codeprivate static final long serialVersionUID = 8683452581122892189L;
serialVersionUID
是 Java 序列化机制中用来验证版本一致性的一个标识。如果你不明确地定义它,Java 序列化机制会根据类的细节自动生成一个版本号,这样一来,当类变动时,会导致旧版本的序列化对象无法和新版本的类进行反序列化。因此,显式声明 serialVersionUID
可以避免在类变更后导致版本不一致的问题。
- DEFAULT_CAPACITY:
private static final int DEFAULT_CAPACITY = 10;
DEFAULT_CAPACITY
是 ArrayList
的默认初始容量。当你创建一个新的 ArrayList
实例时,如果没有指定初始容量,系统会自动分配这个默认值。
- EMPTY_ELEMENTDATA:
private static final Object[] EMPTY_ELEMENTDATA = {};
EMPTY_ELEMENTDATA
是 ArrayList
内部用来表示空列表的空数组。在 ArrayList
初始化时如果没有元素添加进来,elementData
就会指向这个空数组,而不是 null
。
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是在初始化时用来表示初始容量为空(即没有添加元素)的空数组。它主要用于延迟数组的分配,即在添加第一个元素时才会根据 DEFAULT_CAPACITY
分配一个具有默认容量的数组。
- elementData:
transient Object[] elementData;
elementData
是 ArrayList
内部用来存储元素的数组。由于 elementData
是 transient
关键字修饰的,这意味着它不会参与序列化过程。在序列化时,ArrayList
的其他状态会被保存,但 elementData
不会。这是因为 elementData
在反序列化时会被重新初始化,所以不需要持久化。
- size:
private int size;
size
是 ArrayList
中当前元素的数量。它表示 ArrayList
中实际存储的元素个数,而不是 elementData
数组的大小。随着元素的添加和删除,size
会动态变化。
- MAX_ARRAY_SIZE:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
MAX_ARRAY_SIZE
是 ArrayList
中允许的最大数组大小。由于数组的最大索引是 Integer.MAX_VALUE - 1
,因此 MAX_ARRAY_SIZE
留出一些空间以便做边界检查和其他处理,避免溢出和其他问题。
ArrayList的构造方法:
public ArrayList(int initialCapacity)
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,则创建一个具有指定初始容量的
Object
数组elementData
。 - 如果容量等于0,则使用
EMPTY_ELEMENTDATA
,这是一个静态常量,代表一个空的Object
数组。 - 如果容量小于0,抛出
IllegalArgumentException
异常,指示初始容量非法。
public ArrayList()
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
创建一个空的 ArrayList
实例,并指定初始容量为10。
使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,这是一个静态常量,代表一个空的 Object
数组。这个常量的值是一个空数组,以确保 ArrayList
的内部数组不为 null
。
public ArrayList(Collection<? extends E> c)
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实例。
传入参数为要从中获取元素的集合。
- 使用
c.toArray()
获取集合c
中的所有元素并存储在Object[] a
中。 - 检查数组a的长度size,如果不为0,则:
- 如果集合
c
的类型是ArrayList
,直接将a
赋给elementData
。 - 否则,通过
Arrays.copyOf()
方法将a
复制到类型为Object[]
的elementData
中。
- 如果集合
- 如果
size
为0
,将elementData
设置为EMPTY_ELEMENTDATA
,表示一个空的Object
数组。
ArrayList的扩容机制:
在讲ArrayList
方法前要先讲一下ArraylList的扩容机制
ArrayList的扩容机制为:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; // 获取当前数组的容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 计算新的容量,扩容为原来的1.5倍
// 如果计算出的新容量仍然小于 minCapacity,则将新容量设置为 minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量超过了 MAX_ARRAY_SIZE,调用 hugeCapacity(minCapacity) 进行特殊处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用 Arrays.copyOf 方法将 elementData 扩容到新容量,并更新 elementData 的引用
elementData = Arrays.copyOf(elementData, newCapacity);
}
计算新容量:
- 首先获取当前数组的容量
oldCapacity
。 - 计算新的容量
newCapacity
,通常为原容量的 1.5 倍 (oldCapacity + (oldCapacity >> 1)
)。这种扩容策略可以在大多数情况下有效地平衡空间利用率和性能。
比较新容量与 minCapacity:
- 如果计算出的新容量
newCapacity
小于minCapacity
,则将newCapacity
设置为minCapacity
。这样做是为了确保在需要指定最小容量的情况下,扩容后的容量不会小于要求的最小容量。
处理超大容量:
- 如果计算得到的新容量超过了
MAX_ARRAY_SIZE
,这个值是 Java 中数组的最大容量限制,通常是Integer.MAX_VALUE - 8
。当超过这个限制时,调用hugeCapacity(minCapacity)
方法处理,该方法会根据具体需求决定如何处理,比如返回一个合理的大容量。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 检查 minCapacity 是否小于 0,如果是,表示发生了溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE : // 如果 minCapacity 超过了 MAX_ARRAY_SIZE,返回 Integer.MAX_VALUE,即数组的最大可能容量
MAX_ARRAY_SIZE; // 否则返回 MAX_ARRAY_SIZE,限制在合理的最大容量范围内
}
执行扩容:
- 最后,使用
Arrays.copyOf
方法将elementData
数组扩容到新的newCapacity
,并将扩容后的数组赋给elementData
,完成扩容操作。
所以扩容后的长度也不一定是原先长度的1.5倍哦
ArrayList中的方法:
add(E e)
public boolean add(E e) {
ensureCapacityInternal(size + 1);//确保容量足够
elementData[size++] = e;//将最后一位的值设置为e
return true;
}
在数组进行添加操作之前会先进行一个判断ensureCapacityInternal()
方法
ensureCapacityInternal()
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
在这里面会调用ensureExplicitCapacity()
和 calculateCapacity()
方法
calculateCapacity()
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
-
作用:用于计算确保容量时所需的最小容量
-
如果
elementData
是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,即ArrayList
内部数组为空的情况下,默认容量不足以满足minCapacity
,则返回Math.max(DEFAULT_CAPACITY, minCapacity)
,确保容量至少为DEFAULT_CAPACITY
或者minCapacity
中较大的一个。 -
否则,直接返回
minCapacity
,即当前需要的最小容量。
-
ensureExplicitCapacity()
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 作用:
ensureExplicitCapacity
方法确保ArrayList
内部数组的容量至少能够满足minCapacity
。- 首先,增加
modCount
,用于记录结构修改次数。 - 然后,检查当前
elementData
数组的长度是否足以容纳minCapacity
个元素。如果不足,则调用grow(minCapacity)
方法进行扩容操作。
- 首先,增加
add(int index,E element)
这个方法与其说是添加不如说是插入
public void add(int index, E element) {
rangeCheckForAdd(index); // 检查索引的有效性
ensureCapacityInternal(size + 1); // 确保容量足够,增加 modCount
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element; // 将元素放入指定位置
size++; // 更新元素数量
}
System.arraycopy(elementData, index, elementData, index + 1, size - index)
:
- 使用
System.arraycopy
方法进行数组的复制和移动。这一步是为了在插入新元素时,将指定位置及其后面的元素往后移动一位,为新元素腾出空间。具体参数解释:elementData
: 操作的源数组。index
: 起始位置,从这个位置开始向后移动。elementData
: 目标数组,即将元素移动到这个数组。index + 1
: 目标数组的起始位置,从这里开始插入后续的元素。size - index
: 需要移动的元素数量,即从index
到末尾的元素数量。
remove(int index)
移除索引位置中的元素
public E remove(int index) {
rangeCheck(index); // 检查索引是否在有效范围内
modCount++; // 修改计数器加一,用于快速失败机制,继承自AbstractList中的
// 获取被移除的元素的值
E oldValue = elementData(index);
// 计算需要移动的元素个数
int numMoved = size - index - 1;
// 如果有需要移动的元素,则将它们向左移动
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
// 将最后一个元素设置为 null,并减小列表大小
elementData[--size] = null; // 为了让 GC 做回收工作,将最后一个位置置为 null
return oldValue; // 返回被移除的元素的值
}
System.arraycopy(elementData, index + 1, elementData, index, numMoved)
此行代码将 elementData
数组中从 index + 1
开始的部分复制到 index
开始的位置。这样可以将 index
后面的所有元素向左移动一个位置。
remove(Object o)
从ArrayList
中移除指定的元素
public boolean remove(Object o) {
if (o == null) {
// 如果待移除的对象 o 是 null
for (int index = 0; index < size; index++) {
// 遍历 ArrayList 中的元素
if (elementData[index] == null) {
// 找到第一个为 null 的元素
fastRemove(index); // 调用快速移除方法
return true; // 返回 true 表示移除成功
}
}
} else {
// 如果待移除的对象 o 不是 null
for (int index = 0; index < size; index++) {
// 遍历 ArrayList 中的元素
if (o.equals(elementData[index])) {
// 找到与 o 相等的元素
fastRemove(index); // 调用快速移除方法
return true; // 返回 true 表示移除成功
}
}
}
return false; // 没有找到要移除的元素,返回 false
}
fastRemove
和remove
的区别在于一个是不需要进行判断且无需返回值,一个进行了判断且返回的是删除的元素。
removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);//设置为false
}
batchRemove()
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData; // 获取当前 ArrayList 的内部数组
int r = 0, w = 0; // r 是读取索引,w 是写入索引
boolean modified = false; // 记录是否有修改过元素
try {
for (; r < size; r++) {
// 如果元素在集合 c 中存在(或不存在,根据 complement 参数决定)
if (c.contains(elementData[r]) == complement) {
elementData[w++] = elementData[r]; // 将元素复制到新的位置,如果存在就往前移,如果不存在就不改变位置
}
}
} finally {
// 无论 c.contains() 是否抛出异常,都要处理以下逻辑
// 如果 r 不等于 size,说明有元素需要被移动
if (r != size) {
// 使用 System.arraycopy 将剩余的元素移动到正确的位置
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r;
}
// 如果 w 不等于 size,说明有元素需要被清空(后续元素置为 null)
if (w != size) {
// 清空不需要的元素,让 GC 进行回收
for (int i = w; i < size; i++) {
elementData[i] = null;
}
// 更新 modCount,记录修改次数
modCount += size - w;
size = w; // 更新 ArrayList 的大小为 w
modified = true; // 标记已修改
}
}
return modified; // 返回是否有修改过元素
}
感谢这位博主的文章让我弄懂,感谢大佬。
ArrayList 里的 removeAll() 及 batchRemove() 方法【可能让你感受到jdk之美的文章】
set(int index,E e)
public E set(int index, E e) {
rangeCheck(index); // 检查索引范围是否有效
checkForComodification(); // 检查并发修改
// 获取旧值并更新新值
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue; // 返回旧值
}
补充:
transient
关键字
在 Java 中,transient
是一个关键字,用于告诉 JVM 在序列化一个对象时,不需要序列化该字段的值。这个字段在对象被序列化后,就不会被保存在持久化数据中。
ArrayList
中的 elementData
在 ArrayList
类中,elementData
是一个数组,用来存储列表的元素。由于 ArrayList
可能会包含大量的元素,而且元素的类型和数量在运行时可能会变化,因此它是动态管理的。为了优化序列化和反序列化的过程,elementData
被声明为 transient
。
序列化和 elementData
当你使用 Java 中的序列化机制将一个对象持久化到磁盘或通过网络传输时,JVM 会将对象转换为字节流。对于 ArrayList
来说,如果 elementData
不被声明为 transient
,那么在序列化时,整个 elementData
数组会被持久化,这可能会占用大量的存储空间,尤其是当 ArrayList
中有大量元素时。
因此,为了避免不必要的存储和提高序列化的效率,elementData
被标记为 transient
。这意味着在序列化 ArrayList
对象时,JVM 不会将 elementData
的内容写入序列化数据流中。相反,在反序列化时,ArrayList
的其他状态会被还原,而 elementData
则会根据需要重新初始化,以适应反序列化后的对象状态和数据。
transient`。
序列化和 elementData
当你使用 Java 中的序列化机制将一个对象持久化到磁盘或通过网络传输时,JVM 会将对象转换为字节流。对于 ArrayList
来说,如果 elementData
不被声明为 transient
,那么在序列化时,整个 elementData
数组会被持久化,这可能会占用大量的存储空间,尤其是当 ArrayList
中有大量元素时。
因此,为了避免不必要的存储和提高序列化的效率,elementData
被标记为 transient
。这意味着在序列化 ArrayList
对象时,JVM 不会将 elementData
的内容写入序列化数据流中。相反,在反序列化时,ArrayList
的其他状态会被还原,而 elementData
则会根据需要重新初始化,以适应反序列化后的对象状态和数据。
综上:transient
关键字的使用使得 ArrayList
在序列化和反序列化时能够更高效和更合理地管理内部状态,同时减少了序列化数据的大小和复杂度。