Java 集合学习笔记:ArrayList
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合学习笔记:ArrayList相关的知识,希望对你有一定的参考价值。
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 会间接调用此方法)
Java 集合学习笔记:ArrayList