[Java] LinkedList / Queue - 源代码学习笔记

时间:2023-03-08 17:28:02
[Java] LinkedList / Queue - 源代码学习笔记

简单地画了下 LinkedList 的继承关系,如下图。只是画了关注的部分,并不是完整的关系图。本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记。关于 List 接口的笔记,可以参考上一篇博文 List / ArrayList - 源代码学习笔记

[Java] LinkedList / Queue - 源代码学习笔记

Queue

1. 继承 Collection 接口,并提供了额外的插入、提取和查看元素的方法。新增的方法都有两种形式:当操作失败时,抛出异常或者返回一个特殊值。特殊值可以是 null 或者 false ,这取决于方法本身。

   Throws exception Returns special value
Insert add(e) offer(e)
Remove remove() poll()
Examine element() peek()

使用 offer(e) 插入失败时会返回 false ,这一般用于有大小限制的 Queue 实现中。

remove(), poll() 会删除队列的第一个元素,并返回被删除的元素。

element(), peek() 会返回队列的第一个元素,但不会删除它。

表格中罗列的是 Queue 定义的所有方法。

2. 典型的 Queue 按照 FIFO(first-in-first-out) 来排序,不过这不是必须的。例如,优先队列 PriorityQueue 是根据比较器,或者元素自然序来排序的。另一种数据结构 Stack 则是按照 FILO(first-in-last-out) 来排序的。

3. 没有定义阻塞队列 (blocking queue) 的方法。这些方法一般拥有并发编程中,由 java.util.concurrent.BlockingQueue 实现。

4. 应当禁止插入 null 元素,因为 poll(), peek() 方法使用 null 表示当前队列为空。

Deque

1. Deque 代表着 Double Ended Queue ,即双端队列。两端都允许插入、删除元素。

2. 提供了额外的插入、提取和查看元素的方法。新增的方法都有两种形式:当操作失败时,抛出异常或者返回一个特殊值。这点和 Queue 相似。

  First Element (Head) Last Element (Tail)
Throws exception Special value Throws exception Special value
Insert addFirst(e) offerFirst(e)  addLast(e) offerLast(e)
Remove removeFirst() pollFirst() removeLast() pollLast()
Examine getFirst() peekFirst() getLast() peekLast()

通过表格可以观察到,Deque 的方法名以 First 或者 Last 结尾,提供的功能和 Queue 类似,

3. 由于允许双端操作元素,所有 Deque 即可以被当做 Queue 来使用,FIFO(first-in-first-out),也可以被当做 Statck 来使用 FILO(first-in-last-out)。虽然 Deque 提供的功能更多,但是实际应用中,不如 Queue 常用。

4. 无论 Deque 被当做 Queue 还是 Stack 来使用,peek() 方法都能正常被使用,因为它总是返回 Deque 的第一个元素。

5. 应该禁止插入 null 元素。这点和 Queue 相似。

LinkedList

1. 可以简化地理解为实现了 List, Queue 两个接口的集合。

2. 底层的数据结构是双向列表。通过下标查找元素的方法 node(int index) ,先判断离头、尾哪边更近,然后从较近的一端开始找起。

3. 可以包含 null 元素。

4. 迭代器采用 fail-fast 设计思路。这点和 ArrayList 类似。

5. 元素以节点 Node 为单位,其中 Node 是内部类。在看实现代码时可以通过画图来帮助理解,方法实现的代码和 LeetCode 上的练习有些相似。

6. LinkedList 的方法很多,仅用于操作元素的方法就有 24 个,其中,有 6 个是具体的操作实现并被修饰为non-public ,有 18 个是 public 的对外接口。之所有有那么多对外接口,是因为它声明了 Deque 和 Queue 的接口,要实现他们定义全部方法。6 个具体实现分别为:

Link Method Unlinke Method
linkFirst(E) unlinkFirst(Node<E>)
linkLast(E) unlinkLast(Node<E>)
linkBefore(E, Node<E>) unlink(Node<E>)

个人觉得,这个设计使得 LinkedList 功能比较多而杂。

7. indexOf(Object) 和 remove(Object) 方法,在 List 中查找 Object 时,先判断 Object 是否为 null ,然后采用合适的比较方法。以 remove(Object) 为例,代码如下

    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;
}

8. addAll(int index, Collection<? extends E> c) 方法大体思路如下

  a. 通过 c.toArray() 获得待插入元素的数组形式

  b. 从指定的位置 index 开始,依次插入待插入的元素

  c. 将 index 原来的 next 元素指向新插进来的最后一个元素。

9. clear() 方法,删除链表中间的所有连接,帮助垃圾回收器回收内存。

10. toArray():Object 和 toArray(T[]):T[] 方法都会将 List 中的元素全部拷贝到数组中,然后返回这个数组。如果知道 List 元素的类型时,推荐使用 toArray(T[]):T[] ,这样能减少内存分配的开销。

Jdk 版本: jdk1.8.0_31.jdk