Java集合 List实现类 LinkedList 源码浅析
文章目录
- Java集合 List实现类 LinkedList 源码浅析
- 一 、简述(来自JAVA api 注释)
- 二、构造方法
- 三、List方法
- 1、add(E e)
- 2、add(int index, E element)
- 3. 获取指定元素 get(int index)
- 4. 替换元素 set(int index, E element)
- 5.移除元素 remove(int index)
- 6.移除指定元素 remove(Object o)
- 7.链表的元素数目 size()
- 8. 判断是否为空 isEmpty()
- 9.清空链表元素 clear()
- 10.查找指定元素索引 indexOf(Object o)
- 11.判断是否包含指定元素 contains(Object o)
- 12.是否包含指定集合的所有元素 containsAll(Collection<?> c)
- 13.删除包含指定集合的元素 removeAll(Collection<?> c)
- 14.链表转数组 toArray()
- 15.链表转数组, 返回运行时类型 toArray(T[] a)
- 16.排序sort()
- 17. hashCode() 与 equals(Object o)
- 克隆clone() 与序列化 readObject() and writeObject()
- 四、迭代器 Iterator
- 扩展阅读
LinkedList源码实现基于 jdk 1.8
源代码来源于 jdk 1.8
一 、简述(来自JAVA api 注释)
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
注意,此实现不是同步的。 如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:
List list = Collections.synchronizedList(new LinkedList(...));
此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
此类是 Java Collections Framework 的成员。
类图
此处只探讨List 方法的实现, 对于 Queue 和 Deque 队列的实现,稍后有专门的章节探讨.
二、构造方法
LinkedList() 和 LinkedList(Collection<? extends E> c) ;
// 构造一个空列表。
public LinkedList() {
}
/**
* 构造一个包含指定 collection 中的元素的列表,
* 这些元素按其 collection 的迭代器返回的顺序排列。
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
三、List方法
在分析源码前,先看看LinkedList 是如何存储元素的?
LinkedList 里有一个内部类, Node (节点) ;
类型 / 字段 | 描述 |
---|---|
E item | 泛型, 存放着添加进去的元素 |
Node next | 存放下一个节点的指针 |
Node prev | 存放上一个节点的指针 |
LinkedList 里有两个属性 first 和 last
first | 指向第一个节点的指针 |
---|---|
last | 指向最后一个节点的指针 |
由下图可以看出LinkedList 是双链表, 并且是不循环的链表.此链表的特性是内存利用率高, 删除和添加元素快.可以向前搜索, 也可以向后搜索
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
1、add(E e)
源码分析: 声明一个记录尾节点的指针 l, l = last;新建一个节点 new Node<>(l, e, null), prev = last, next = null,
即前一个指针指向原来的尾节点, 下一个节点为null; 如果为节点l为null, 头节点等于尾节点; 如果不为null,原来的尾节点的下一个节点的指针指向当前新键的节点 newNode.
时间复杂度为 O(1)
// 将指定元素添加到此列表的结尾。
public boolean add(E e) {
linkLast(e);
return true;
}
// 链接e作为最后一个元素。
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
2、add(int index, E element)
源码注释: 将指定元素插入此列表中的指定位置。 将当前位置的元素(如果有)和任何后续元素向右移动(将其添加到索引中)。
源码解析: 添加元素前, 检查索引是否越界, 如果越界跑错异常;
如果: 索引等于链表长度, 则添加到链表的尾部;
如果: 索引小于链表长度,则先获取当前索引的节点, 在把元素添加到这个节点的前面;
时序图
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
// 检查索引是否有效,否则抛出 IndexOutOfBoundsException 异常
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 判断参数是否是迭代器或添加操作的有效位置的索引。
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
// 构造一个下标越界的异常信息,(java.lang.IndexOutOfBoundsException: Index: 44, Size: 3)
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
// 返回指定元素索引处的(非null)节点。
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 在非null节点succ之前插入元素e。
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
3. 获取指定元素 get(int index)
源码注释: 返回此列表中指定位置的元素。
源码分析: 先检查元素索引, 然后调用 node() 获取指定索引的元素;
获取元素的算法为遍历链表, 时间复杂度为 O(n)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 检查元素索引, 如果索引超出范围 (index < 0 || index >= size())
// 则抛出异常
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 判断参数是否是现有元素的索引。
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
4. 替换元素 set(int index, E element)
源码注释: 用指定的元素替换此列表中指定位置的元素。
源码分析: 检查索引, 调用 node() 方法获取指定位置的节点, 然后替换节点的元素 x.item = element;
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
5.移除元素 remove(int index)
源码注释: 删除此列表中指定位置的元素。 将任何后续元素向左移位(从索引中减去一个元素)。 返回从列表中删除的元素。
源码分析: 首先检查元素索引, 然后调用 node 获取指定索引的节点, 再调用 unlink 断开这个节点;
unlink 会判断这个节点是否为首节点first 或是尾节点 last
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 断开非空节点 x
// 这个方法的操作大致分三段, 输入需要断开的节点 x,获取 x节点的前一个指针/下一个节点指针/当前元素指针
// 1.判断前一个节点指针是否为null,如果为true, 即节点x 就是首节点 first
// 2.判断下一个节点指针是否为null, 如果为true, 即节点x 就是尾节点 last
// 3.当前元素指针置空, size--
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
6.移除指定元素 remove(Object o)
源码注释: 从该列表中删除指定元素的第一个匹配项(如果存在)。 如果此列表不包含该元素,则不会更改。 更正式地,删除具有最低索引i的元素,使得 (o == null ? get(i)==null : o.equals(get(i))) (如果存在这样的元素)。 如果此列表包含指定的元素,则返回true(或等效地,如果此列表因调用而更改)。
源码分析: 代码的逻辑 , 则迭代链表,使用参数 o与每个元素比较, 找到最早匹配到的元素, 最后调用 unlink() 方法断开节点
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
7.链表的元素数目 size()
源码注释: 返回此列表中的元素数。
public int size() {
return size;
}
8. 判断是否为空 isEmpty()
源码注释: 如果此collection不包含任何元素,则返回true。
此实现返回 size() == 0
public boolean isEmpty() {
return size() == 0;
}
9.清空链表元素 clear()
源码注释: 从此列表中删除所有元素。 此调用返回后,列表将为空。
源码分析: 迭代链表把每个节点的指针置空, 此操作后 size = 0, 首节点和尾节点指针为null, modCount 计数器 +1
public void clear() {
//清除节点之间的所有链接是“不必要的”,但是:
// - 如果丢弃的节点居住,则帮助生成GC
//不止一代
// - 即使有可达的迭代器,也一定要释放内存
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
10.查找指定元素索引 indexOf(Object o)
源码注释: 返回此列表中第一次出现的指定元素的索引,如果此列表不包含该元素,则返回-1。
更正式地,返回最低索引i,使得 (o==null ? get(i)==null : o.equals(get(i))) ,如果没有这样的索引则返回-1。
源码分析: 此处的逻辑与 remove(Object o) 方法的逻辑一样, 只是最后一步的操作有差异.此处最后一步是返回匹配到的元素索引
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
11.判断是否包含指定元素 contains(Object o)
源码注释: 如果此列表包含指定的元素,则返回true。 更正式地,当且仅当此列表包含至少一个元素e时才返回true
(o == null ? e == null : o.equals(e))
源码分析: 这个方法是indexOf() 方法的扩展版本;
public boolean contains(Object o) {
return indexOf(o) != -1;
}
12.是否包含指定集合的所有元素 containsAll(Collection<?> c)
源码注释: 如果此collection包含指定collection中的所有元素,则返回true。
此实现迭代指定的集合,依次检查迭代器返回的每个元素以查看它是否包含在此集合中。 如果包含所有元素,则返回true,否则返回false。
源码分析: 首先迭代参数集合c, 使用集合c的每个元素作为contains()方法的参数, 搜索元素.如果出现一次 false, 马上返回, 即链表不包含这个集合c的所有元素;
这个时间复杂度可以理解为 O(n^2), 因为此处是两个for循环嵌套
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
13.删除包含指定集合的元素 removeAll(Collection<?> c)
源码注释: 删除此集合的所有元素,这些元素也包含在指定的集合中(可选操作)。 此调用返回后,此集合将不包含与指定集合相同的元素。
此实现迭代此集合,依次检查迭代器返回的每个元素,以查看它是否包含在指定的集合中。 如果包含它,则使用迭代器的remove方法将其从此集合中删除。
请注意,如果迭代器方法返回的迭代器未实现remove方法,并且此collection包含与指定collection相同的一个或多个元素,则此实现将抛出UnsupportedOperationException。
源码分析: 先判断c是否非空, 再获取当前链表的迭代器, 调用集合c 的 contains()方法搜索迭代器返回的元素it.next;如果返回true就调用迭代器的 remove()方法移除这个元素;
由此可知, 假若获取的迭代器不支持 remove方法,则抛出异常 UnsupportedOperationException
default void remove() {
throw new UnsupportedOperationException("remove");
}
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
14.链表转数组 toArray()
源码注释: 以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。
返回的数组将是“安全的”,因为此列表不会保留对它的引用。 (换句话说,此方法必须分配一个新数组)。 因此调用者可以自由修改返回的数组。
此方法充当基于阵列和基于集合的API之间的桥梁。
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
15.链表转数组, 返回运行时类型 toArray(T[] a)
源码注释: 以适当的顺序返回包含此列表中所有元素的数组(从第一个元素到最后一个元素);返回数组的运行时类型是指定数组的运行时类型。如果列表适合指定的数组,则返回其中。否则,将为新数组分配指定数组的运行时类型和此列表的大小。
如果列表适合指定的数组,并且有空余空间(即,数组的元素多于列表),则紧跟在列表末尾的数组中的元素将设置为null。 (仅当调用者知道列表不包含任何null元素时,这在确定列表长度时很有用。)
与toArray()方法一样,此方法充当基于数组的API和基于集合的API之间的桥梁。此外,该方法允许精确控制输出阵列的运行时类型,并且在某些情况下可以用于节省分配成本。
假设x是已知仅包含字符串的列表。以下代码可用于将列表转储到新分配的String数组中:
String [] y = x.toArray(new String [0]);
请注意,toArray(new Object [0]) 在功能上与toArray() 相同。
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
16.排序sort()
源码注释: 根据指定的比较器引发的顺序对此列表进行排序。
此列表中的所有元素必须使用指定的比较器进行相互比较(即,c.compare(e1,e2) 不得对列表中的任何元素e1和e2抛出ClassCastException)。
如果指定的比较器为null,则此列表中的所有元素都必须实现Comparable接口,并且应使用元素的自然顺序。
此列表必须是可修改的,但无需调整大小。
源码分析: 这个方法带有 default 关键字, 是 List 接口里的默认方法
源码逻辑, 输入一个比较器Comparator; 先调用 toArray() 获取一个新数组 a, 再排序数组a;
然后获取当前链表的迭代器, 再遍历a,把a 的每个值重新设置到链中, 因为 set()方法不会修改链表的结构, 所以不会抛出 ConcurrentModificationException
这个排序的效率关键在于 Arrays.sort() 方法的时间复杂度
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
17. hashCode() 与 equals(Object o)
hashCode() 源码注释: 返回此列表的哈希码值。
此实现完全使用用于在List.hashCode方法的文档中定义列表哈希函数的代码。
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
equals 源码注释:
将指定对象与此列表进行比较以获得相等性。 当且仅当指定的对象也是列表时,返回true,两个列表具有相同的大小,并且两个列表中的所有对应元素对都相等。 (如果 (e1 == null ? e2 == null : e1.equals(e2)).) 则两个元素e1和e2相等。)换句话说,如果两个列表包含相同顺序的相同元素,则它们被定义为相等。
此实现首先检查指定的对象是否为此列表。 如果是,则返回true; 如果不是,它检查指定的对象是否是列表。 如果不是,则返回false; 如果是这样,它迭代两个列表,比较相应的元素对。 如果任何比较返回false,则此方法返回false。 如果迭代器在另一个之前耗尽了元素,则返回false(因为列表的长度不等); 否则在迭代完成时返回true。
源码逻辑: 判断列表是否相等的条件, 大小相同, 顺序相同, 里面的元素必须满足 (o1 == null ? o2 == null : o1.equals(o2))
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
扩展讨论:
这里有一个比较有趣的现象, 我们看看 eclipse 自动生成的 equals 和 hashcode 函数
为什么, 运算hash 值要 使用 31 作为基数?
据说是31为素数, 这主要利用了素数的数学特性. 比如只能被1 和 自己整除, 能够尽可能地避免了哈希冲突
public class HQExample {
private int hash;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + hash;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HQExample other = (HQExample) obj;
if (hash != other.hash)
return false;
return true;
}
}
克隆clone() 与序列化 readObject() and writeObject()
a) clone()
源码注释: 返回此LinkedList的浅表副本。 (元素本身未被克隆。)
public Object clone() {
LinkedList<E> clone = superClone();
// Put clone into "virgin" state
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
b) readObject()
源码注释: 从流中重构此LinkedList实例(即反序列化它)。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
c) writeObject()
源码注释: 将此LinkedList实例的状态保存到流(即将其序列化)。
@serialData
列表的大小(它包含的元素的数量)被发出(int),然后是正确顺序的所有元素(每个都是一个Object)。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
四、迭代器 Iterator
1. 获取迭代器 iterator()
源码注释: 返回此列表中元素的迭代器(按正确顺序)。此实现仅返回列表上的列表迭代器。
源码分析: 此处逻辑可知, iterator()返回的迭代器和listIterator()返回的迭代器都是 ListItr 的实例, 因此都是可以向前和向后遍历的
public Iterator<E> iterator() {
return listIterator();
}
2. 获取 listIterator()
源码注释: 返回此列表中元素的列表迭代器(按适当顺序)。此实现返回 listIterator(0)。
public ListIterator<E> listIterator() {
return listIterator(0);
}
3. 指定索引的 listIterator(int index)
源码注释: 从列表中的指定位置开始,返回此列表中元素的列表迭代器(按正确顺序)。 遵守List.listIterator(int )的一般合约。
list-iterator是快速失败的:如果在创建Iterator之后的任何时候对列表进行结构修改,除了通过list-iterator自己的remove或add方法之外,list-iterator将抛出ConcurrentModificationException。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
4.迭代器的实现类 ListItr
类图
ListItr 源码
源码分析: ListItr 实现了 Iterator 和ListIterator ,
private class ListItr implements ListIterator<E> {
// 最后返回的节点
private Node<E> lastReturned;
// 下一个节点
private Node<E> next;
// 下一个索引
private int nextIndex;
// 并发修改的标记
private int expectedModCount = modCount;
// 初始化时, lastReturned = null, next = node(index)(这个索引的节点, 比如0,就是首节点)
// nextIndex = index, expectedModCount = modCount(即这个链表被修改过的次数)
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
}
可以调用的方法有 hasNext()、next、remove()、hasPrevious()、previous()、add(E e)、set(E e)
方法 | 说明 |
---|---|
hasNext() | 如果仍有元素可以迭代,则返回 true。 |
next() | 返回迭代的下一个元素。 |
remove() | 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 |
hasPrevious() | 如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。 |
previous() | 返回列表中的前一个元素。 |
add(E e) | 将指定的元素插入列表(可选操作)。 |
set(E e) | 用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。 |
hasNext()
源码注释: 如果此列表迭代器在向前遍历列表时具有更多元素,则返回true。 (换句话说,如果next会返回一个元素而不是抛出异常,则返回true。)
public boolean hasNext() {
return nextIndex < size;
}
next()
源码注释: 返回列表中的下一个元素并前进光标位置。 可以重复调用此方法以遍历列表,或者与之前的调用混合以来回传递。 (注意,对next和previous的交替调用将重复返回相同的元素。)
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
hasPrevious()
源码注释: 如果此列表迭代器在反向遍历列表时具有更多元素,则返回true。(换句话说,如果previous先前返回一个元素而不是抛出异常,则返回true。)
public boolean hasPrevious() {
return nextIndex > 0;
}
previous()
源码注释: 返回列表中的上一个元素并向后移动光标位置。 可以重复调用此方法以向后遍历列表,或者与来回调用混合以来回来。 (注意,对next和previous的交替调用将重复返回相同的元素。)
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
remove()
源码注释: 从列表中删除next或previous返回的最后一个元素(可选操作)。 此呼叫只能在每次呼叫下一次或上一次时进行一次。 只有在最后一次调用next或previous之后才调用add时,才能进行此操作。
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
set(E e)
源码注释: 用指定的元素替换next或previous返回的最后一个元素(可选操作)。 只有在最后一次调用next或previous之后才调用remove和add时,才能进行此调用。
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
add(E e)
源码注释: 将指定的元素插入列表(可选操作)。 元素将紧接在next将返回的元素之前插入(如果有),并且在之前返回的元素之后插入(如果有)。 (如果列表中不包含任何元素,则新元素将成为列表中的唯一元素。)新元素在隐式游标之前插入:对next的后续调用不受影响,后续调用previous将返回新元素。 (此调用将调用nextIndex或previousIndex返回的值增加1。)
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
其他方法:
// 返回对 next 的后续调用所返回元素的索引。
public int nextIndex() {
return nextIndex;
}
// 返回对 previous 的后续调用所返回元素的索引。
public int previousIndex() {
return nextIndex - 1;
}
// 对每个剩余元素执行给定的操作,直到所有元素都被处理或动作引发异常。
// 这个方法在 Iterator里的默认实现的方法
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
扩展阅读
ArrayList 实现 | ArrayList 列表实现浅析 |
LinkedList 实现 | LinkedList 双链表实现浅析 |
2018-12-13 持续更新…