Java 集合学习笔记:LinkedList
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合学习笔记:LinkedList相关的知识,希望对你有一定的参考价值。
Java 集合学习笔记:LinkedList
UML
简介
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。
所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。
注意,此实现不是同步的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:List list = Collections.synchronizedList(new LinkedList(...));
此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
阅读源码
增
访问修饰符&返回类型 | 方法 | 描述 |
---|---|---|
boolean | add(E e) | 将指定元素添加到此列表的结尾。 |
void | add(int index, E element) | 在此列表中指定的位置插入指定的元素。 |
boolean | addAll(Collection<? extends E> c) | 添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。 |
boolean | addAll(int index, Collection<? extends E> c) | 将指定 collection 中的所有元素从指定位置开始插入此列表。 |
void | addFirst(E e) | 将指定元素插入此列表的开头。 |
void | addLast(E e) | 将指定元素添加到此列表的结尾。 |
boolean | offer(E e) | 将指定元素添加到此列表的末尾(最后一个元素)。 直接调 add(E e) 。 |
boolean | offerFirst(E e) | 在此列表的开头插入指定的元素。 内部调 addFirst(e) 成功返回 true 。 |
boolean | offerLast(E e) | 在此列表末尾插入指定的元素。 内部调 addLast(e) 成功返回 true 。 |
void | push(E e) | 将元素推入此列表所表示的堆栈。 直接调 addFirst(e) 。 |
boolean add(E e)
- test
@Test
public void addTest()
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
list.add("XYZ");
log.info(list.toString()); // [A, B, C, D, E, XYZ]
add(E e)
:将指定元素添加到此列表的结尾。
public boolean add(E e)
linkLast(e);
return true;
linkLast(E e)
:链接e
作为最后一个元素。
void linkLast(E e)
// 临时缓存末尾节点
final Node<E> l = last;
// 创建新节点,亲节点插在末尾,所以它后面为 null
final Node<E> newNode = new Node<>(l, e, null);
// 更新末尾节点指针变量
last = newNode;
// 如原末尾节点就是 null 说明,列表是空的。头节点也指向新节点。
// 否则:原节点的上一个节点更新 next 指向新节点
if (l == null)
first = newNode;
else
l.next = newNode;
size++; // 插入节点成功,长度++
modCount++; // 插入节点成功,修改计数++
Node<E>
:节点类,私有。
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;
void add(int index, E element)
- test
@Test
public void addIndexTest()
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
list.add(2, "XYZ");
log.info(list.toString()); // [A, B, XYZ, C, D, E]
- 插入流程
- 找到
index
对应的目标节点R
。 - 将
R
与它的前一节点L
断开。 - 创建新节点
newNode
。使newNode.prev
指向L
,newNode.next
指向R
。 - 更新
R
节点指针:R.prev
指向newNode
。 - 更新
L
节点指针:
5.1. 如果节点L
为 null,表示我们插入在了头部,更新头节点指针first = newNode
5.2. 否则L.next
指向newNode
。
add(int index, E element)
在列表的index
处插入element
。
public void add(int index, E element)
checkPositionIndex(index); // 检查 index 如果越界就抛锅
if (index == size) // 如果是末尾
linkLast(element); // 直接连接
else
// 否则 在非空 index 节点之前插入元素 element。
linkBefore(element, node(index));
checkPositionIndex(int index)
索引检测,如果越界就抛锅。
private void checkPositionIndex(int index)
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
isPositionIndex(int index)
索引在正常范围内,返回true
private boolean isPositionIndex(int index)
// index = 0 表示头节点。
// index = size 表示尾节点的 next。也就是末尾追加元素的位置。
return index >= 0 && index <= size;
- linkLast(E e):将元素
e
作为最后一个节点,连接到链表。
node(int index)
按索引获取节点。返回指定元素索引处的(非空)Node。
Node<E> node(int index)
// size / 2 找出中间值,index 小于它说明靠左,从头开始找,否则从末尾开始找。
if (index < (size >> 1))
// 先拿到头节点
Node<E> x = first;
// 从前向后找,直到遇上 index 的前一个节点,取它的 next 返回。
for (int i = 0; i < index; i++)
x = x.next;
return x;
else
// 先拿到尾节点
Node<E> x = last;
// 从后向前找,直到遇上 index 的后一个节点,取它的 prev 返回。
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
void linkBefore(E e, Node<E> succ)
// 断言 succ != null; add 开头有检测过 index 有效,肯定能找到一个节点。
// 获取【原节点 succ】的前一节点。
final Node<E> pred = succ.prev;
// 创建存放【元素e】的新节点,同时指定其【前、后】指针。
final Node<E> newNode = new Node<>(pred, e, succ);
// 更新【原节点 succ】的 prev 指针,指向新节点。
succ.prev = newNode;
// 如果【原节点 succ】的 prev 为 null 说明它当时就是头节点,
// 现在新节点插入在原节点前面,所以更新头节点指针,指向新插入的节点。
if (pred == null)
first = newNode;
// pred.next 本来指向【原节点 succ】,现在要更新为指向新插入节点。
else
pred.next = newNode;
// 插入成功,更新列表长度。
size++;
// 插入成功,更新修改计数。
modCount++;
addAll(Collection<? extends E> c)
- test
@Test
public void addAllTest()
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
list.addAll(Arrays.asList("1","2","3"));
log.info(list.toString()); // [A, B, C, D, E, 1, 2, 3]
将集合 c
原样插入当前列表末尾。
public boolean addAll(Collection<? extends E> c)
// 尾节点的索引是 size - 1。在它后面插入就是 size
return addAll(size, c);
boolean addAll(int index, Collection<? extends E> c)
- test
@Test
public void addAllIndexTest()
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
list.addAll(2, Arrays.asList("1","2","3"));
log.info(list.toString()); // [A, B, 1, 2, 3, C, D, E]
addAll(int index, Collection<? extends E> c)
将指定集合 c
中的所有元素从指定位置 index
开始插入到此列表中。此列表中 index
及其后的所有元素,向后挪。
checkPositionIndex 索引检测,如果越界就抛锅。
// 向 ----- 索引2处插入 ===
// 断开为 -- 和 ---
// 插入 -- === ---
// 更新连接处各节点的 prev、next 指针
// 完成插入 --===---
// 插入的是集合 [===,+++,***] 就重复如上。
// --===---
// --===+++---
// --===+++***---
public boolean addAll(int index, Collection<? extends E> c)
checkPositionIndex(index); // 索引检测,如果越界就抛锅。
Object[] a = c.toArray(); // 获取集合 c 所有元素的数组。
int numNew = a.length; // 获取将插入的元素个数
if (numNew == 0) // 如果插入元素个数为 0 直接返回 false
return false;
// 声明两个临时变量,在插入过程中,用来临时保存前后节点。
// pred=前节点(原本就在 index 节点前面,新节点来后,位置不变)
// succ=后节点(插入前 index 位置的节点,被新节点往后挤了)
Node<E> pred, succ;
// 如果 index == size 在尾节点后追加
if (index == size)
succ = null; // 后节点自然还是 null
pred = last; // 原列表的尾节点。
// 否则在中间或头插入
else
succ = node(index); // index 所在节点,作为后节点。
pred = succ.prev; // index节点的 prev,作为前节点。
// 遍历数组 a 逐个插入
for (Object o : a)
// 取出元素,强转类型,以符合当前列表。
@SuppressWarnings("unchecked") E e = (E) o;
// 创建存放元素的新节点:【前节点】上面已取得直接使,【后节点】留空,稍后处理。
Node<E> newNode = new Node<>(pred, e, null);
// 没人出头,我出头当大哥。
if (pred == null)
first = newNode;
// 有带头大哥,就让大哥带新节点。新节点在创建时 prev 已经指向 pred ,至此双向连接完成。
else
pred.next = newNode;
// 更新前节点变量。用这个的新节点作为后续节点的【前节点】
pred = newNode;
// 遍历插入完成后,处理尾节点。
// 在插入前,插入位置就没有【后节点】,那最后一个插入的节点作为【尾节点】
if (succ == null)
last = pred;
// 否则:新插入的最后一个节点与 succ 连接。
else
pred.next = succ;
succ.prev = pred;
// 更新列表长度 +上新插入的个数
size += numNew;
// 更新修改计数。与批量插入了多少个元素无关,计为修改了 1 次。
modCount++;
// 插入成功返回 true
return true;
Object[] toArray()
将当前表中的元素,按顺序复制到一个新数组并返回。此数组与原列表无引用关系,可以随便编辑修改数组
(元素是传值址的,与原列表共享,修改元素内容,两边都会变的
)。
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;
void addFirst(E e)
- test
@Test
public void addFirstTest()
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
list.addFirst("X");
log.info(list.toString()); // [X, A, B, C, D, E]
void addFirst(E e)
public void addFirst(E e)
linkFirst(e);
void linkFirst(E e)
将元素e
作为头节点
,连接到链表。
private void linkFirst(E e)
// 取出【头节点】,存入临时变量 f
final Node<E> f = first;
// 创建新节点,存放元素 e,并将其 next 指向 f (原来的头节点)
final Node<E> newNode = new Node<>(null, e, f);
// 更新当前链表的【头节点】指向【新节点】
first = newNode;
// 如果是空链表,更新当前链表的【尾节点】指向【新节点】
if (f == null)
last = newNode;
// 否则:【原来的头节点】的 prev 指向【新节点】
else
f.prev = newNode;
// 插入成功,更新列表长度。
size++;
// 插入成功,更新修改计数。
modCount++;
void addLastTest()
- test
@Test
public void addLastTest()
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D", "E"));
list.addLast("X");
log.info(list.toString()); // [A, B, C, D, E, X]
void addLast(E e)
public void addLast(E e)
linkLast(e);
- linkLast(E e):将元素
e
作为最后一个节点,连接到链表。
删
访问修饰符&返回类型 | 方法 | 描述 |
---|---|---|
void | clear() | 从此列表中移除所有元素。 |
E | remove() | 获取并移除此列表的头(第一个元素)。 底层调用 removeFirst() 并返回结果。 |
E | remove(int index) | 移除此列表中指定位置处的元素。 |
boolean | remove(Object o) | 从此列表中移除首次出现的指定元素(如果存在)。 |
E | removeFirst() | 移除并返回此列表的第一个元素。 |
boolean | removeFirstOccurrence(Object o) | 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。 直接调用 remove(Object o) 并返回结果。 |
E | removeLast() | 移除并返回此列表的最后一个元素。 |
boolean | removeLastOccurrence(Object o) | 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。 |
E | Java集合源码学习笔记LinkedList分析
Java学习笔记5.2.2 List接口 - LinkedList类 |