Java 集合学习笔记:ArrayList
UML
从类图可见ArrayList
并不是直接实现 List
。
ArrayList
是继承 AbstractList
再进行扩展的。
至于实现List
接口的行为,听说是失误,反正删不删也不影响,所以就一直没动。
简介
ArrayList 是 List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是非线程安全的。)
List
的 数组
实现。
- 因为底层数据解构是
数组
,所以特点是连续内存空间,根据索引操作。 - 优势在于,频繁写操作相对吃亏。有两个性能消耗点:
2.1. 插入/删除时,当前元素之后的所有元素需要整体平移。
2.2. 空间不足时的反复自动扩容。
阅读源码
增
方法 | 说明 |
---|---|
boolean add (E e) | 在列表末尾添加新元素。 |
void add (int index, E element) | 在指定位置添加元素,原 index 到末尾的所有元素,整体后移一位。 |
boolean addAll (Collection<? extends E> c) | 将指定集合添加 到当前列表的末尾 。 |
boolean addAll (int index, Collection<? extends E> c) | 将指定集合插入 当前列表的指定位置 。 |
- public boolean
add
(E element)
public boolean add(E e) {
// 当前长度 + 1 = 所需长度。检测并按需扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 在末尾插入新元素,并更新 size
elementData[size++] = e;
// 成功返回 true
return true;
}
- public void
add
(int index, E element)
public void add(int index, E element) {
// 检查 index 有没有超出合理范围,插入的合理索引范围是: 0 到 size
rangeCheckForAdd(index);
// 当前长度 + 新增长度1 = 所需长度。检测并按需扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// elementData 是当前 list 底层的数组容器。
// 将 index 后的所有元素整体向右移一位,让出空间,插入新元素。
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 更新 index 位置元素的值
elementData[index] = element;
// 更新当前 list 大小
size++;
}
- public boolean
addAll
(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
// 取出 c 的底层数组
Object[] a = c.toArray();
// 获取将会新增多少个元素(也就是 a 的长度)
int numNew = a.length;
// 当前长度 + 新增长度 = 所需长度。检测并按需扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 从数组 a 的第 0 个索引开始,复制 numNew 个元素,到 elementData 的末尾(size)。
System.arraycopy(a, 0, elementData, size, numNew);
// 更新当前 list 的大小(+上新增个数)
size += numNew;
// 如果新增个数不为 0 表示插入成功,返回 true
return numNew != 0;
}
- public boolean
addAll
(int index, Collection<? extends E> c)
// 在当前 list 的 index 处插入集合 c (c保持其迭代器的顺序,整个拷进来)
public boolean addAll(int index, Collection<? extends E> c) {
// 检查 index 有没有超出合理范围,插入的合理索引范围是: 0 到 size
rangeCheckForAdd(index);
// 取出 c 的底层数组
Object[] a = c.toArray();
// 获取将会新增多少个元素(也就是 a 的长度)
int numNew = a.length;
// 当前长度 + 新增长度 = 所需长度。检测并按需扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// numMoved 表示:在当前 list 中的 index 处插入 c 时 index 后有多少个元素要向右平稳让出空位。
int numMoved = size - index;
// numMoved 大于 0 表示不在末尾,肯定有元素需要右移。
// 调用 native 方法,利用复制实现平稳。(原来的位置数据还在,但不用管,插入新元素会覆盖它们)
// elementData 是当前 list 底层的数组容器。
// 从 index 位置开始,复制 numMoved 个元素到 index + numNew 处。
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
// 从数组 a 的第 0 个索引开始,复制 numNew 个元素,到 elementData 的 index 处。
System.arraycopy(a, 0, elementData, index, numNew);
// 更新当前 list 的大小(+上新增个数)
size += numNew;
// 如果新增个数不为 0 表示插入成功,返回 true
return numNew != 0;
}
删
方法 | 说明 |
---|---|
void clear () | 清空列表。 |
E remove (int index) | 移除指定索引上的元素。右侧所有元素整体向左平移一位。 |
boolean remove (Object o) | 移除指定元素。如果有多个,每次只会移除第一个。 |
boolean removeAll (Collection<?> c) | 删除指定集合中包含的元素。 |
boolean removeIf (Predicate<? super E> filter) | 删除所有复合条件的元素。(filter 是一个返回布尔型的 Lambda) |
boolean retainAll (Collection<?> c) | 保留当前列表 与目标集合 中都存在 的元素。也就是,从当前列表中删除指定集合中不存在的所有元素。 |
- public boolean
removeAll
(Collection<?> c)
public boolean removeAll(Collection<?> c) {
// 如果要删除的集合 c 为 null 直接抛锅。
Objects.requireNonNull(c);
// 否则调用批量删除方法。(注意这里给的值 false)
return batchRemove(c, false);
}
// c :给定的集合
// complement : true ? 跳过 : 删除 (以下注释以 false 为例)
private boolean batchRemove(Collection<?> c, boolean complement) {
// 取当前 list 底层数组容器
final Object[] elementData = this.elementData;
// 双指针删除算法。(本质是把不需要删除的元素从 r 复制到 w 保留下来)
// 可以把 r 想象成原数组中的指针,把 w 想象成结果数组中的指针。
int r = 0, w = 0;
// 设定修改标记,表示未修改。
boolean modified = false;
try {
// 本质就是从原数组向结果数据复制元素,遇到要删除的就跳过。
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// 保持与AbstractCollection的行为兼容性,即使c.contains()抛出。
// r != size 表示遍历没走完,中途抛异常了。
if (r != size) {
// 把 r 后所有元素,向前移到 w 位置。(后续元素虽然没处理,但也不能弄丢了)
System.arraycopy(elementData, r, elementData, w, size - r);
// 更新结果指针的值
w += size - r;
}
// 如果结果指针 w 与 size 不一样,说明有元素被删除了。
if (w != size) {
// 把 w 之后的所有索引上的内容都清理掉。(这些元素都已经被移走了,这里是残留的垃圾)
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w; // 更新修改计数
size = w; // 更新 size
modified = true; // 更新修改标记
}
}
return modified; // 返回修改标记
}
- 双指针删除算法 JS 演示
本质就是从原数组向结果数据复制元素,遇到要删除的就跳过。结果数组自然就没有要删除的元素了。
分开声明 arr1
、arr2
是为了便于理解,懂了之后不需要arr2
全都用arr1
,效果是一样的。
var arr1 = [1,2,3,4,5]; // 原数组
var arr2 = arr1; // 结果数组
var removeArr = [2, 4]; // 要删除的元素
var r = 0; // 指向原数组
var w = 0; // 指向结果数组
for(;r < arr1.length; r++){
// 该元素不需要删除,我们就执行:从原数组拷贝到结果数组
if (removeArr.includes(arr1[r]) == false)
arr2[w++] = arr1[r];
}
// 抛弃末尾的无效元素
arr2.length = arr1.length - removeArr.length;
console.info(arr2);
改
方法 | 说明 |
---|---|
E set (int index, E element) | 替换指定位置的元素 |
void replaceAll (UnaryOperator operator) | 遍历列表执行特定的操作,实现替换。list.replaceAll(x -> x * 2); |
void sort (Comparator<? super E> c) | 传入一个比较器。list.sort((a,b) -> b-a); |
查
方法 | 说明 |
---|---|
size () | 获取列表大小。 |
boolean isEmpty () | 判断列表是否为空。 |
E get (int index) | 按索引获取元素。 |
boolean contains (Object o) | 判断列表是否包含指定元素。 |
int indexOf (Object o) | 返回指定元素在列表中的索引位置 。 |
int lastIndexOf (Object o) | 返回指定元素最后一次出现的位置。 |
手动扩容/缩容
方法 | 说明 |
---|---|
void ensureCapacity (int minCapacity) | 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。 |
void trimToSize () | 将此 ArrayList 实例的容量调整为列表的当前大小。 |
迭代
方法 | 说明 |
---|---|
void forEach (Consumer<? super E> action) | 遍历列表执行消费者。 |
Iterator iterator () | 返回迭代器 |
Spliterator spliterator () | 返回可拆分迭代器。 |
ListIterator listIterator () | 返回双向迭代器。 |
ListIterator listIterator (int index) | 从指定的位置开始,返回双向迭代器。 |
List subList (int fromIndex, int toIndex) | 返回指定范围的subList |
Object[] toArray () | 返回 Object 数组 |
<T> T[] toArray (T[] a) | 返回指定类型数组。 list.toArray(new Integer[0]); |
内部类
类 | 说明 |
---|---|
class Itr implements Iterator | 实现了 Iterator 接口,iterator() 返回的就是它。 |
class ListItr extends Itr implements ListIterator | 双向迭代器。listIterator() 返回的就是它。 |
private class SubList extends AbstractList implements RandomAccess | 私有内部类。subList() 返回的就是它。 |
Itr
定义迭代器
,用于在调用 iterator()
时,返回当前列表的迭代器
。
private class Itr implements Iterator<E> {
int cursor; // 光标指向:下一次(要返回的)元素的索引。 int 类型默认值 0
int lastRet = -1; // 最近一次(返回的)元素的索引; 调用 remove 后会重置为-1,表示没有指向。
int expectedModCount = modCount; // 预期的结构修改的次数 = 此列表被结构修改的次数
Itr() {}
// 如果下一次(要返回的)元素,不等于最后一个。返回 true
public boolean hasNext() {
return cursor != size;
}
//
@SuppressWarnings("unchecked")
public E next() {
// 检测并发冲突
checkForComodification();
// 申明变量 i 缓存下一个(要返回的)元素的索引。
int i = cursor;
// 如果 i 比最后一个元素(size-1)还大,则抛锅。
if (i >= size)
throw new NoSuchElementException();
// 取列表底层数组容器。如果 i 大于底层数组的长度。抛锅。
Object[] elementData = ArrayList.this.elementData;
// i 大于等于数组长度,说明数组被别人动了,抛并发异常锅。
if (i >= elementData.length)
throw new ConcurrentModificationException();
else{
// 否则更新下一个(要返回的)元素的索引 +1
cursor = i + 1;
}
// 然后从底层数组容器中获取索引 i 对应的元素。
// 同时更新最后一次操作的元素 lastRet 等于这个返回的元素索引。(下面 remove 就要用到 lastRet )
return (E) elementData[lastRet = i];
}
// 删除 next() 返回的索引,每次 next() 后才可以 remove 一次。
// Itr 只有这一个方法会修改列表。
// 最后一次(操作的)元素的索引小于 0,等于 -1 时表示没有。抛锅。
// 检查并发冲突。
// 执行删除操作
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 上一次 next() 的最后更新了 lastRet,移除它。
ArrayList.this.remove(lastRet);
// 更新下一个(要返回的)元素的索引等于 lastRet。
// 比如 lastRet = 0 时 cursor = 1。当上面执行删除索引后,后面所有元素向前移到,next() 应该返回的还是索引 0 对应的元素。
cursor = lastRet;
// 上一次操作的元素已经删除。重置 lastRet 为 -1 实现一次 next() 对应一个 remove()
lastRet = -1;
// 同步预期修改次数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
/** 遍历余下的所有元素,执行消费者 */
public void forEachRemaining(Consumer<? super E> consumer) {
// 非空检测,为空抛锅。
Objects.requireNonNull(consumer);
// 取当前列表的 size
final int size = ArrayList.this.size;
// 申明变量 i 缓存下一个(要返回的)元素的索引。
// 如果 i 大于列表尺寸,抛锅。
int i = cursor;
if (i >= size) { return; }
// 取列表底层数组容器。如果 i 大于底层数组的长度。抛锅。
final Object[] elementData = ArrayList.this.elementData;
// i 大于等于数组长度,说明数组被别人动了,抛并发异常锅。
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
// 从下一个(要返回的)元素开始,一直到最后一个元素。逐个执行消费者。
// 每次都要同步 modCount == expectedModCount
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// 下一个(要返回的)元素索引更新为 i(最后一个元素的后一位)
cursor = i;
// 更新最后一次(操作作的)元素的索引
lastRet = i - 1;
// 检查并发冲突。
checkForComodification();
}
// 检查并发冲突。Itr 的 remove 会同步 expectedModCount = modCount;
// 如果这两个变量不相等,说明当前列表被其他未知的神秘力量修改过了。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListItr
定义双向迭代器
,用于在调用 listIterator()
时,返回当前列表的双向迭代器
。
/** 扩展 Itr 实现 ListIterator 接口定义的方法。*/
private class ListItr extends Itr implements ListIterator<E> {
// 传入光标指向的索引。初始化双向迭代器。
ListItr(int index) {
super();
cursor = index;
}
// 如果下一个(要返回的)元素的索引不等于 0 返回 true
public boolean hasPrevious() {
return cursor != 0;
}
// 返回下一个(要返回的)元素的索引
public int nextIndex() {
return cursor;
}
// 返回上一个(要返回的)元素的索引。
public int previousIndex() {
return cursor - 1;
}
/** 返回上一个元素。*/
@SuppressWarnings("unchecked")
public E previous() {
// 检查并发冲突。
checkForComodification();
// 取上一个元素索引。小于 0 就抛锅。
int i = previousIndex();
if (i < 0)
throw new NoSuchElementException();
// 取列表底层数组容器。如果 i 大于底层数组的长度。抛锅。
Object[] elementData = ArrayList.this.elementData;
// i 大于等于数组长度,说明数组被别人动了,抛并发异常锅。
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 更新光标指向上一个元素
cursor = i;
// 更新最后一次操作的元素的索引
// 返回这个元素
return (E) elementData[lastRet = i];
}
/** 用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。*/
public void set(E e) {
// 最后一次(操作的)元素的索引小于 0,等于 -1 时表示没有。抛锅。
// 检查并发冲突。
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 更新最后一次操作的元素。
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/** 将指定的元素插入列表(可选操作)。 */
public void add(E e) {
// 检查并发冲突
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
// 光标指向,向后+1
cursor = i + 1;
// 清空最后一个操作元素的指针。
lastRet = -1;
// 每次都要同步 modCount == expectedModCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
静态内部类
类 | 说明 |
---|---|
static final class ArrayListSpliterator<E> implements Spliterator<E> | 实现了可拆分迭代器接口。 |
自动扩容逻辑
以下方法会触发自动扩容。(从JDK8中数的)
// 手动扩容
public void ensureCapacity(int minCapacity)
// 末尾追加
public boolean add(E e)
// 插入指定位置(ListItr、SubList 会间接调用此方法)
public void add(int index, E element)
// 末尾追加给定集合
public boolean addAll(Collection<? extends E> c)
// 指定位置插入给定集合(SubList 会间接调用此方法)
public boolean addAll(int index, Collection<? extends E> c)
没有自动缩容,觉得自己拿捏的可以手动缩容。
// 将此 ArrayList 实例的容量调整为列表的当前大小。
public void trimToSize()
Java7
– | 说明 |
---|---|
Object[] elementData; | 被封装的数组对象 :ArrayList 内部真正储存数据的容器。自动扩容其实就是在折腾 elementData 。 |
int size; | 元素个数: ArrayList 中实际存储元素的个数。 |
DEFAULT_CAPACITY = 10; | 默认初始容量 |
Object[] EMPTY_ELEMENTDATA = {}; | 初始化容器 :所有ArrayList 都用它初始化,避免创建无数个空数组浪费。 |
默认扩容因子 | oldCapacity + (oldCapacity >> 1) : 原容量 x 1.5 |
扩容方式 | 1. 调用 ensureCapacity(int minCapacity) 手动扩容。2. add 时自动检测并按需扩容。3. addAll 时自动检测并按需扩容。4. readObject(ObjectInputStream s) 时自动检测并按需扩容。 |
minCapacity
:最小所需容积
newCapacity
:新容积
- 计算新容积
newCapacity
:
1.1. 默认扩容1.5倍。
1.2. 如果1.5不够,则扩容到所需大小minCapacity
。
1.3. 如果新容积newCapacity
>MAX_ARRAY_SIZE
则进一步判断:(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
- 底层调用
Arrays.copyOf(elementData, newCapacity)
创建新数组,再将结果
赋给原容器elementData
。
2.1.Arrays.copyOf
的底层是native
原生方法System.arraycopy
Java8
- | 说明 |
---|---|
int DEFAULT_CAPACITY = 10; | 默认初始容量。(容量:指底层数组实际大小) |
Object[] EMPTY_ELEMENTDATA = {}; | 列表大小为 0 时共享此空数组。避免每个空列表都创建一个空数组,浪费。 |
Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; | 1. 初始列表时,没给默认大小,用此空数组。 2. 扩容时用于作为判断条件之一。 |
Object[] elementData; | 内部容器,底层实际用于保存数据的数组。 |
size | 列表长度。(容器当前存了几个元素)。 |
默认扩容因子 | 新容量 = 旧容量 + (旧容量 >> 1) = 原容量 x 1.5 |
具体的直接看源码吧。。。
扩容 - 核心代码
/**
* 在此列表中的指定位置插入指定元素。
* 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)。
*/
public void add(int index, E element) {
// 检查 index 如果越界就抛异常。
rangeCheckForAdd(index);
// 判断内部数组 elementData 大小,如果有需要就扩容。同时列表修改次数 modCount++
ensureCapacityInternal(size + 1);
// 将目标索引后面的所有元素整体向后移一位。用的原生方法 System.arraycopy
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 当前索引位置赋值
elementData[index] = element;
// 列表大小+1
size++;
}
/**
* 计算内部数组 elementData 最小所需容量
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 判断内部数组 elementData 大小,如果有需要就扩容。
ensureExplicitCapacity(minCapacity);
}
/**
* 判断内部数组 elementData 大小,如果有需要就扩容。
*/
private void ensureExplicitCapacity(int minCapacity) {
// 列表修改次数 modCount++
modCount++;
// 最小所需容量 > 数组原长度则扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 扩容,以确保至少要能下 minCapacity 个元素。
*/
private void grow(int minCapacity) {
// 原容量
int oldCapacity = elementData.length;
// 新容量 = 原容量 + (原容量 / 2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 新容量够用就用新容量,不够就按 minCapacity 来。
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 新容量大于最大限制,就使用最大限制。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 原数组 = 复制原数组中的数据,并按 newCapacity 创建的新数组。
elementData = Arrays.copyOf(elementData, newCapacity);
}
移除 - 核心代码
E remove(int index)
public E remove(int index) {
rangeCheck(index); // 检查索引,越界就抛异常
modCount++; // 列表修改次数 +1
E oldValue = elementData(index); // 缓存当前索引值,删除完成后 return 出去
// 利用 arraycopy 将索引后的全部元素整体前移一位,覆盖当前索引值,达到删除目的。
int numMoved = size - index - 1; // 计算需要整体向左移动的元素个数
// 如果有,就复制(以复制实现移动效果)
// 将数组 elementData 中 index+1 位置开始的元素,复制到 index 处。(也就是向前移一位)
// 需要复制的元素个数就是 numMoved (也就是被删除的元素右侧的所有元素)
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// 所有数据复制后向左移一位粘贴,最右侧一格现在就需要销毁了。
elementData[--size] = null; // --size 实现长度减1。同时也将最后一个元素=null 以便垃圾回收。
// 返回被删除的值
return oldValue;
}
boolean remove(Object o)
// 遍历数组逐个比对,找到第一个复合的元素就移除。
// 唯一区别就在于 null 和普通对象做了区别处理
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 对比
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 在 remove(int index) 基础上做了简化,省去了对【索引的校验】和【删除值的返回】。
// 因为这里是通过对比对象得出的 index 绝对是存在的。
private void fastRemove(int index) {
modCount++;
// 计算删除 index 位置元素后,它后面有多少个元素需要前移一位。
// 要使用的元素个数 = 总个数 - 删除的索引 - 1
int numMoved = size - index - 1;
if (numMoved > 0)
// 将被删除元素后紧接着的 numMoved 个元素向前移支一位。
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// 最后一个位置清空,待GC清理。同时总个数 - 1
elementData[--size] = null; // clear to let GC do its work
}
根据 remove 我们可以看到,每次执行只会移除第一个匹配到的元素。
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, null, 3, 4, 5, null));
list.remove(null);
System.out.println(list);
// [1, 2, 3, 4, 5, null]
boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c); // 如果 c 为 null 抛异常
return batchRemove(c, false); //
}
/**
* 批量移除
* @param c collection 从当前列表中的元素,如果 c 也有,那么根据 complement 决定去留。
* @param complement 判断对 c 中包含的元素 true=保留,false=移除
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r 表示被检测的索引。用于遍历原数组,从 0 一直加到 size-1,逐个检测元素。
// w 表示用于保存的位置。配合 r 每判定保留(移动的元素就是要保留下来的)一个元素 +1。最终 w 也就是最后一个留存元素的索引。
int r = 0, w = 0;
boolean modified = false;
try {
// 本质上是两层循环:第一层 for 遍历 elementData
// 第二层用 c.contains 进行了简化。判断 c 中是否包含 elementData的当前元素。
// 如果不包含,就是要保留的元素。将元素从 r 移动到 w。然后 w 向后移一位。
// 否则遇到需要移除的元素时,只需要跳过移动操作。r++ 但 w 没动,下个元素移过就实现了。
// 假设第一个元素就是要移除的,那么移动就是:1到0, 2到1,3到2,以此类推,从第二个元素开始整体向前平移一位。
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
// 将要保留的元素从 r 移到 w
elementData[w++] = elementData[r];
} finally {
// r 没走到 size 只有一种可能就是异常了。(暂时还没想到什么情况能触发异常)
// 后面的不处理的,没移动的都一把移过来。
// 最后更新 w = w + 刚才这波移动的个数
if (r != size) {
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r;
}
// w 之后都是要移除的,全部置空,等GC回收
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;
}
学习总结
-
插入:如果插入多个连续的元素,可以使用
addAll
优化 。只需要整体平移一次。 -
扩容:如果能提前确定所需容积,可以手动指定大小来实现优化。省掉多次扩容浪费时间。
-
删除:如果是删除连续的几个元素可以使用
removeRange(int fromIndex, int toIndex)
,底层只需要arraycopy
整体平移一次。
3.1. 从当前列表中移除 fromIndex(包括)到 toIndex(不包括)之间的所有元素。向左平移所有后续元素。
3.2. 当然这个方法没有直接开放给用户使用。可以list.subList(3, 5).clear();
间接调它。
3.3.subList.clear()
>List.clear()
>AbstractList.clear()
>>AbstractList.removeRange()
>ArrayList.removeRange()
大概就是这么兜了一圈,实现的。 -
遍历删除:遍历中要删除元素的场景,请用迭代器或Java8的
removeIf
。详情见:笑虾:forEach 遍历中 remove 的BUG,及 Java8 的新推荐。 -
效率:底层的数据结构为数组,基于索引操作。相对来说:查询效率高,增删效率低。
参考资料
笑虾:Java 集合学习笔记:Iterator
笑虾:Java 集合学习笔记:Collection