死磕java集合之ArrayList源码分析(JDK18)
文章目录
- 死磕java集合之ArrayList源码分析(JDK18)
- 简介
- 继承体系
- 源码解析
- 属性
- ArrayList(int initialCapacity)构造方法
- ArrayList()构造方法
- ArrayList(Collection<? extends E> c)构造方法
- add(E e)方法
- add(E e, Object[] elementData, int s)方法
- add(int index, E element)方法
- addAll(Collection<? extends E> c)方法
- addAll(int index, Collection<? extends E> c)方法
- get(int index)方法
- set(int index, E element)方法
- remove(int index)方法
- remove(Object o)方法
- retainAll(Collection<?> c)方法
- removeAll(Collection<?> c)
- removeIf(Predicate<? super E> filter)
- 总结
- 彩蛋
简介
ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可称之为动态数组。
继承体系
ArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口。
ArrayList实现了List,提供了基础的添加、删除、遍历等操作。
ArrayList实现了RandomAccess,提供了随机访问的能力。
ArrayList实现了Cloneable,可以被克隆。
ArrayList实现了Serializable,可以被序列化。
源码解析
属性
/**
* 默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,如果传入的容量为0时使用
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 集合中元素的个数
*/
private int size;
// AbstractList中的属性
// 这个列表在结构上被修改的次数。结构修改是指改变列表的大小,或者以一种可能产生不正确结果的方式扰乱列表。
protected transient int modCount = 0;
(1)DEFAULT_CAPACITY
默认容量为10,也就是通过new ArrayList()创建时的默认容量。
(2)EMPTY_ELEMENTDATA
空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。
(3)DEFAULTCAPACITY_EMPTY_ELEMENTDATA
也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。
(4)elementData
真正存放元素的地方,使用transient是为了不序列化这个字段。
至于没有使用private修饰,后面注释是写的“为了简化嵌套类的访问”,但是楼主实测加了private嵌套类一样可以访问。
private表示是类私有的属性,只要是在这个类内部都可以访问,嵌套类或者内部类也是在类的内部,所以也可以访问类的私有成员。
(5)size
真正存储元素的个数,而不是elementData数组的长度。
ArrayList(int initialCapacity)构造方法
传入初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果传入的初始容量大于0,就新建一个数组存储元素
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果传入的初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
ArrayList()构造方法
不传初始容量,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会在添加第一个元素的时候扩容为默认的大小,即10。
public ArrayList() {
// 如果没有传入初始容量,则使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// 使用这个数组是在添加第一个元素的时候会扩容到默认大小10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList(Collection<? extends E> c)构造方法
传入集合并初始化elementData,这里会使用拷贝把传入集合的元素拷贝到elementData数组中,如果元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组。
/**
* 把传入集合的元素初始化到ArrayList中
*/
public ArrayList(Collection<? extends E> c) {
// 集合转数组
Object[] a = c.toArray();
if ((size = a.length) != 0) {
// 如果c的类型为ArrayList
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 如果不是,拷贝成Object[].class类型,并赋值给elementData
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 如果c的空集合,则初始化为空数组EMPTY_ELEMENTDATA
elementData = EMPTY_ELEMENTDATA;
}
}
为什么c.toArray();
返回的有可能不是Object[]类型呢?请看下面的代码:
public class ArrayTest {
public static void main(String[] args) {
Father[] fathers = new Son[]{};
// 打印结果为class [Lcom.coolcoding.code.Son;
System.out.println(fathers.getClass());
List<String> strList = new MyList();
// 打印结果为class [Ljava.lang.String;
System.out.println(strList.toArray().getClass());
}
}
class Father {}
class Son extends Father {}
class MyList extends ArrayList<String> {
/**
* 子类重写父类的方法,返回值可以不一样
* 但这里只能用数组类型,换成Object就不行
* 应该算是java本身的bug
*/
@Override
public String[] toArray() {
// 为了方便举例直接写死
return new String[]{"1", "2", "3"};
}
}
add(E e)方法
添加元素到末尾,平均时间复杂度为O(1)。
public boolean add(E e) {
// ArrayList结构有变化,modCount加1
modCount++;
// 将e加入elementData中
add(e, elementData, size);
return true;
}
add(E e, Object[] elementData, int s)方法
private void add(E e, Object[] elementData, int s) {
// 如果当前elementData数组中真实的元素个数s(size)已达到elementData的长度,则需要扩容
if (s == elementData.length)
// 扩容
elementData = grow();
// 在最后一个元素上,加入e
elementData[s] = e;
// size加1
size = s + 1;
}
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
// 旧容量
int oldCapacity = elementData.length;
// 如果旧容量大于0,或elementData数组不为空数组
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 这里最小growth为minCapacity - oldCapacity,最佳growth为旧容量的一半
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
// 复制element数组到新数组,并使新数组长度为newCapacity,并赋值给elementData
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
// 如果elementData为空数组,数组长度取默认长度10和minCapacity的最大值
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
// 这个函数在类ArraysSupport中
// 在给定数组的当前长度、最小增长量和首选增长量的情况下,计算新的数组长度
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0
// 得到首选长度
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
// 如果首选长度大于0小于等于软最大阵列长度(Integer.MAX_VALUE - 8)
// 由阵列增长计算施加的软最大阵列长度。实际的限制可能取决于一些JVM实现特定的特征,比如对象头的大小。软最大值的选择是保守的,以便小于可能遇到的任何实现限制
return prefLength;
} else {
// put code cold in a separate method
// 计算得到一个大的长度值
return hugeLength(oldLength, minGrowth);
}
}
// 这个函数在类ArraySupport中
private static int hugeLength(int oldLength, int minGrowth) {
int minLength = oldLength + minGrowth;
if (minLength < 0) { // overflow
// 如果minLength超过了Integer的范围
throw new OutOfMemoryError(
"Required array length " + oldLength + " + " + minGrowth + " is too large");
} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
// 如果小于等于SOFT_MAX_ARRAY_LENGTH
return SOFT_MAX_ARRAY_LENGTH;
} else {
// 如果大于SOFT_MAX_ARRAY_LENGTH,小于等于Integer.MAX_VALUE
return minLength;
}
}
// 复制指定的数组,用空值截断或填充(如果需要),使复制具有指定的长度
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
// 复制指定的数组,用空值截断或填充(如果需要),使复制具有指定的长度
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 按照newType的class类型创建一个长度为newLength的数组
@SuppressWarnings("unchecked")
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;
}
- modCount加1;
- 检查是否需要扩容;
- 需要扩容时,正常扩容至原来的1.5倍,特殊的扩容至SOFT_MAX_ARRAY_LENGTH或(SOFT_MAX_ARRAY_LENGTH, Integer.MAX_VALUE]
- 将e加入elementData的索引size位;
- size加1。
add(int index, E element)方法
添加元素到指定位置,平均时间复杂度为O(n)。
public void add(int index, E element) {
// 检查是否越界
rangeCheckForAdd(index);
// modCount加1
modCount++;
final int s;
Object[] elementData;
// 如果需要扩容
if ((s = size) == (elementData = this.elementData).length)
// 扩容
elementData = grow();
// 将inex及其之后的元素往后挪一位,则index位置处就空出来了
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
// 将元素插入到index的位置
elementData[index] = element;
// size加1
size = s + 1;
}
- 检查索引是否越界;
- modCount加1;
- 检查是否需要扩容
- 需要需要扩容时,正常扩容至原来的1.5倍,特殊的扩容至SOFT_MAX_ARRAY_LENGTH或(SOFT_MAX_ARRAY_LENGTH, Integer.MAX_VALUE]
- 在索引index处插入元素element;
- size加1。
addAll(Collection<? extends E> c)方法
求两个集合的并集,如果原ArrayList有变化,返回true,无变化返回false
public boolean addAll(Collection<? extends E> c) {
// 集合转数组
Object[] a = c.toArray();
// modCount加1
modCount++;
int numNew = a.length;
// 如果集合为空,返回false
if (numNew == 0)
return false;
Object[] elementData;
final int s;
// 如果集合的大小比elementData当前空闲的长度大时,需要扩容
if (numNew > (elementData = this.elementData).length - (s = size))
// 扩容(将容量扩充到原始容量的1.5倍或s+numNew大小
elementData = grow(s + numNew);
// 把数组a中的元素拷贝到elementData的尾部
System.arraycopy(a, 0, elementData, s, numNew);
// 更新size
size = s + numNew;
return true;
}
addAll(int index, Collection<? extends E> c)方法
在指定索引位置,加入集合c求并集,如果原ArrayList有变化,返回true,无变化返回false
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否在[0,size]范围内
rangeCheckForAdd(index);
// 集合c转数组
Object[] a = c.toArray();
// modCount加1
modCount++;
int numNew = a.length;
// 如果集合为空,返回false
if (numNew == 0)
return false;
Object[] elementData;
final int s;
// 如果集合的大小比elementData当前空闲的长度大时,需要扩容
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
// 得到需要移动的数组部分的长度
int numMoved = s - index;
if (numMoved > 0)
// 如果索引不在size时
// 移动原数组的[index, size -1]到[index + numNew, index + numNew + numMoved - 1]
System.arraycopy(elementData, index,
elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size = s + numNew;
return true;
}
get(int index)方法
获取指定索引位置的元素,时间复杂度为O(1)
public E get(int index) {
// 检查是否越界,看索引index是否在[0,size)内
Objects.checkIndex(index, size);
// 返回数组index位置的元素
return elementData(index);
}
// 在Object类中
@ForceInline
public static int checkIndex(int index, int length) {
return Preconditions.checkIndex(index, length, null);
}
// 在Preconditions类中
@IntrinsicCandidate
public static <X extends RuntimeException> int checkIndex(int index, int length,
BiFunction<String, List<Number>, X> oobef) {
if (index < 0 || index >= length)
// 这里oobef为null
throw outOfBoundsCheckIndex(oobef, index, length);
return index;
}
// 在Preconditions类中
private static RuntimeException outOfBoundsCheckIndex(
BiFunction<String, List<Number>, ? extends RuntimeException> oobe,
int index, int length) {
// 这里oobe为null
return outOfBounds(oobe, "checkIndex", index, length);
}
private static RuntimeException outOfBounds(
BiFunction<String, List<Number>, ? extends RuntimeException> oobef,
String checkKind,
Number... args) {
List<Number> largs = List.of(args);
// 这里oobef为null
RuntimeException e = oobef == null
? null : oobef.apply(checkKind, largs);
// 这里e为null,checkKind为"checkIndex"
return e == null
? new IndexOutOfBoundsException(outOfBoundsMessage(checkKind, largs)) : e;
}
// 处理抛出的异常的异常内容,这里checkKind为"checkIndex"
private static String outOfBoundsMessage(String checkKind, List<? extends Number> args) {
if (checkKind == null && args == null) {
return String.format("Range check failed");
} else if (checkKind == null) {
return String.format("Range check failed: %s", args);
} else if (args == null) {
return String.format("Range check failed: %s", checkKind);
}
int argSize = 0;
switch (checkKind) {
case "checkIndex":
// 走到这里
argSize = 2;
break;
case "checkFromToIndex":
case "checkFromIndexSize":
argSize = 3;
break;
default:
}
// Switch to default if fewer or more arguments than required are supplied
switch ((args.size() != argSize) ? "" : checkKind) {
case "checkIndex":
// 走这里
return String.format("Index %s out of bounds for length %s",
args.get(0), args.get(1));
case "checkFromToIndex":
return String.format("Range [%s, %s) out of bounds for length %s",
args.get(0), args.get(1), args.get(2));
case "checkFromIndexSize":
return String.format("Range [%s, %<s + %s) out of bounds for length %s",
args.get(0), args.get(1), args.get(2));
default:
return String.format("Range check failed: %s %s", checkKind, args);
}
}
E elementData(int index) {
return (E) elementData[index];
}
- 检查索引是否越界,越界抛出IndexOutOfBoundsException异常(异常内容为“Index index out of bounds for length size”);
- 返回索引位置的元素。
set(int index, E element)方法
在索引index处设置一个新值,并返回旧值
public E set(int index, E element) {
// 检查是否越界,看索引index是否在[0,size)内
Objects.checkIndex(index, size);
// 获取索引处数组的旧值
E oldValue = elementData(index);
// 设定新值
elementData[index] = element;
// 返回旧值
return oldValue;
}
remove(int index)方法
删除指定索引位置的元素,时间复杂度为O(n)。
public E remove(int index) {
// 检查索引是否越界
Objects.checkIndex(index, size);
// 使用es来作为变量,引用elementData,这里是引用传递,后面es的变动都会反映到elementData
final Object[] es = elementData;
// 得到旧值
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
// 删除旧值
fastRemove(es, index);
// 返回旧值
return oldValue;
}
private void fastRemove(Object[] es, int i) {
// modCount加1
modCount++;
final int newSize;
// 如果删除的不是最后一位
if ((newSize = size - 1) > i)
// 索引i后面的元素往前移一位
System.arraycopy(es, i + 1, es, i, newSize - i);
// size减1,并将索引size处的值置为null,方便GC
es[size = newSize] = null;
}
- 检查索引是否越界;
- 得到旧值;
- 快速删除;
- modCount加1;
- 如果删除的不是最后一位,则索引i后面的元素都往前移1位
- size减1,并将索引size处的值置为null,方便GC
- 返回旧值。
remove(Object o)方法
删除指定元素值的元素,时间复杂度为O(n)。
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
// 找到es中第一次出现o的索引i,找不到则返回false
found: {
if (o == null) {
for (; i < size; i++)
// 如果要删除的元素为null,则以null进行比较,使用==
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
// 如果要删除的元素不为null,则进行比较,使用equals()方法
if (o.equals(es[i]))
break found;
}
return false;
}
// 找到符合条件的索引i,快速删除
fastRemove(es, i);
return true;
}
- 找到第一个等于指定元素值的元素,如果找不到返回false;
- 找到了,快速删除,并返回true。
注意:
当传入的参数是整数时,需要区分是基础数据类型int,还是包装类Integer,如果是int则采用remove(int index),如果是Integer则采用remove(Object o)。
retainAll(Collection<?> c)方法
求两个集合的交集。
public boolean retainAll(Collection<?> c) {
return batchRemove(c, true, 0, size);
}
// 按条件批量删除ArrayList中[from,end)的元素
// complement为true,删除ArrayList中所有不包含在集合c中的元素,得到的是ArrayList与集合c的交集
// complement为false,删除ArrayList中所有包含在集合c中的元素,得到的是ArrayList与集合c的差集
boolean batchRemove(Collection<?> c, boolean complement,
final int from, final int end) {
// 要求集合c不能为空
Objects.requireNonNull(c);
final Object[] es = elementData;
int r;
// Optimize for initial run of survivors
for (r = from;; r++) {
if (r == end)
// complement为true,解释为:ArrayList中的元素全在集合c中,返回false,表示未删除元素
// complement为false,解释为:没有交集,ArrayList中的所有元素都不在集合c中,返回false,表示未删除元素
return false;
if (c.contains(es[r]) != complement)
// complement为true,解释为在ArrayList中找到第一个不在集合c中的元素的索引r
// completion为false,解释为在ArrayList中找到第一个在集合c中的元素的索引r
break;
}
// w指向找到的索引r, r取下一位r+1
int w = r++;
try {
for (Object e; r < end; r++)
if (c.contains(e = es[r]) == complement)
// complement为true,解释为:在ArrayList中找到一个在集合c中的元素的,并将这个元素写入es[w],并将w加1
// complement为false,解释为:在ArrayList中找到一个元素不在集合c中的元素,并将这个元素写入es[w],并将w加1
es[w++] = e;
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 保持行为与AbstractCollection兼容
// 将[r,end -1]的元素写到[w, end - r + w - 1]中
System.arraycopy(es, r, es, w, end - r);
// 此时w应向后移end - r
w += end - r;
throw ex;
} finally {
// 变动的元素为end-w个
modCount += end - w;
// 擦除[w, end)之间的元素,end等于size时,不需要移动元素
shiftTailOverGap(es, w, end);
}
// 有删除元素,返回true
return true;
}
// 在Object类中
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
// 找到的索引大于等于0,说明集合包含这个元素o
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
// 在[start, end)这个范围内查找o的索引
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = start; i < end; i++) {
// 如果要删除的元素为null,则以null进行比较,使用==
if (es[i] == null) {
return i;
}
}
} else {
for (int i = start; i < end; i++) {
// 如果要删除的元素不为null,则进行比较,使用equals()方法
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
// 擦除[lo, hi)之间的元素,并将[hi, size-1]的元素前移 hi - lo 位
private void shiftTailOverGap(Object[] es, int lo, int hi) {
// 将[hi,size - 1]的元素复制到[lo, size - 1 - (hi - lo)]
System.arraycopy(es, hi, es, lo, size - hi);
// 擦除[size - (hi - lo), size - 1]的元素
for (int to = size, i = (size -= hi - lo); i < to; i++)
es[i] = null;
}
- 遍历elementData数组;
- 如果元素在c中,则把这个元素添加到elementData数组的w位置并将w位置往后移一位;
- 遍历完之后,w之前的元素都是两者共有的,w之后(包含)的元素不是两者共有的;
- 将w以后(包含)的元素置为null,方便GC回收。
removeAll(Collection<?> c)
求两个集合的单方向差集,只保留当前集合中不在c中的元素,不保留在c中不在当前集体中的元素。
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false, 0, size);
}
与retainAll(Collection<?> c)方法类似,只是这里保留的是不在c中的元素。
removeIf(Predicate<? super E> filter)
从ArrayList中删除满足给定谓词filter的所有元素
@Override
public boolean removeIf(Predicate<? super E> filter) {
return removeIf(filter, 0, size);
}
// 删除满足给定谓词的所有元素,从索引i(包含)到索引end(排除)。如果存在删除行为,返回true;无删除行为,返回false
boolean removeIf(Predicate<? super E> filter, int i, final int end) {
// 检查filter是否为null
Objects.requireNonNull(filter);
int expectedModCount = modCount;
final Object[] es = elementData;
// Optimize for initial run of survivors
// 找到第一个满足给定谓词filter的元素的索引i
for (; i < end && !filter.test(elementAt(es, i)); i++)
;
// Tolerate predicates that reentrantly access the collection for
// read (but writers still get CME), so traverse once to find
// elements to delete, a second pass to physically expunge.
if (i < end) {
// 开始遍历
final int beg = i;
final long[] deathRow = nBits(end - beg);
// 将i记录在deathRow[0]
deathRow[0] = 1L; // set bit 0
for (i = beg + 1; i < end; i++)
// 如果满足函数式断言
if (filter.test(elementAt(es, i)))
// 记录需要删除元素的下标
setBit(deathRow, i - beg);
if (modCount != expectedModCount)
// 标记过程出现多线程并发修改,则抛出 ConcurrentModificationException
throw new ConcurrentModificationException();
modCount++;
int w = beg;
for (i = beg; i < end; i++)
// 从第一个被删除的位置开始,将需要保留的元素顺序进行填充
if (isClear(deathRow, i - beg))
es[w++] = es[i];
// 擦除[w, end)之间的元素
shiftTailOverGap(es, w, end);
return true;
} else {
// 没找到满足给定谓词的索引
if (modCount != expectedModCount)
// 处理过程中发生结构型变化
throw new ConcurrentModificationException();
return false;
}
}
// 取得es数组上指定索引的元素
static <E> E elementAt(Object[] es, int index) {
return (E) es[index];
}
private static long[] nBits(int n) {
return new long[((n - 1) >> 6) + 1];
}
private static void setBit(long[] bits, int i) {
bits[i >> 6] |= 1L << i;
}
private static boolean isClear(long[] bits, int i) {
return (bits[i >> 6] & (1L << i)) == 0;
}
总结
- ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;
- ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
- ArrayList添加元素到尾部极快,平均时间复杂度为O(1);
- ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
- ArrayList从尾部删除元素极快,时间复杂度为O(1);
- ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);
- ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;
- ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;
- ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;
彩蛋
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
// 防止序列化期间有修改
int expectedModCount = modCount;
// 写出非transient非static属性(会写出size属性)
s.defaultWriteObject();
// Write out size as capacity for behavioral compatibility with clone()
// 写出元素个数
s.writeInt(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();
}
}
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
// 读入非transient非static属性(会读取size属性)
s.defaultReadObject();
// Read in capacity
// 读入元素个数,没什么用,只是因为写出的时候写了size属性,读的时候也要按顺序来读
s.readInt(); // ignored
if (size > 0) {
// like clone(), allocate array based upon size not capacity
// 就像克隆,分配数组的长度是基于size而不是capacity(即有多少,用多少)
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
Object[] elements = new Object[size];
// Read in all elements in the proper order.
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
}
elementData = elements;
} else if (size == 0) {
// size等于0,elementData赋值{}
elementData = EMPTY_ELEMENTDATA;
} else {
// size小于0,抛出异常
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}
查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。
一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。
在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。
elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。
本文是参照彤哥的文章写的,故文末附上彤哥的文章链接,可点击链接查看彤哥的更多文章。