1、结构

image.png

2、源码要点

2.1、接口Deque

相比于ArrayList,它额外实现了双端队列接口Deque,这个接口主要是声明了队头,队尾的一系列方法。

2.2、类成员

image.png
LinkedList内部有两个引用,一个first,一个last,分别用于指向链表的头和尾,另外有一个size,用于标识这个链表的长度,
而它的接的引用类型是Node,这是他的一个内部类:
image.png
item用于保存数据,而prve用于指向当前节点的前一个节点,next用于指向当前节点的下一个节点。

2.3、add(E e)方法

这个方法直接调用linkLast:
执行过程,一开始,first和last都为null,此时链表什么都没有,当第一次调用该方法后,first和last均指向了第一个新加的节点E1:
image.png
接着,第二次调用该方法,加入新节点E2。首先,将last引用赋值给l,接着new了一个新节点E2,并且E2的prve指向l,接着将新节点E2赋值为last。现在结构如下:
image.png
接着判断l==null? 所以走的else语句,将l的next引用指向新节点E2,现在数据结构如下:
image.png
接着size+1,modCount+1,退出该方法,局部变量l销毁,所以现在数据结构如下:
image.png
这样就完成了链表新节点的构建。

2.4、add(int index, E element)

这个方法是在指定位置插入新元素

  1. public void add(int index, E element) {
  2. checkPositionIndex(index);
  3. if (index == size)
  4. linkLast(element);
  5. else
  6. linkBefore(element, node(index));
  7. }
  1. index位置检查(不能小于0,大于size)
  2. 如果index==size,直接在链表最后插入,相当于调用add(E e)方法
  3. 小于size,首先调用node方法将index位置的节点找出,接着调用linkBefore
    1. void linkBefore(E e, Node<E> succ) {
    2. // assert succ != null;
    3. final Node<E> pred = succ.prev;
    4. final Node<E> newNode = new Node<>(pred, e, succ);
    5. succ.prev = newNode;
    6. if (pred == null)
    7. first = newNode;
    8. else
    9. pred.next = newNode;
    10. size++;
    11. modCount++;
    12. }
    我们同样作图分析,假设现在链表中有三个节点,调用node方法后找到的第二个节点E2,则进入方法后,结构如下:
    image.png
    接着,将succ的prev赋值给pred,并且构造新节点E4,E4的prev和next分别为pred和suc,同时将新节点E4赋值为succ的prev引用,则现在结构如下:
    image.png
    接着,将新节点赋值给pred节点的next引用,结构如下:
    image.png

最后,size+1,modCount+1,推出方法,本地变量succ,pred销毁,最后结构如下:
image.png

2.5、E get(int index)

获取指定节点数据

  1. public E get(int index) {
  2. checkElementIndex(index);
  3. return node(index).item;
  4. }

直接调用node方法:

  1. Node<E> node(int index) {
  2. // assert isElementIndex(index);
  3. if (index < (size >> 1)) {
  4. Node<E> x = first;
  5. for (int i = 0; i < index; i++)
  6. x = x.next;
  7. return x;
  8. } else {
  9. Node<E> x = last;
  10. for (int i = size - 1; i > index; i--)
  11. x = x.prev;
  12. return x;
  13. }
  14. }
  1. 判断index在链表的哪边。
  2. 遍历查找index或者size-index次,找出对应节点。
    这里我们知道,相比于数组的直接索引获取,遍历获取节点效率并不高。

    2.6、E remove(int index)

    移除指定节点

    1. public E remove(int index) {
    2. checkElementIndex(index);
    3. return unlink(node(index));
    4. }
  3. 检查index位置

  4. 调用node方法获取节点,接着调用unlink(E e)

    1. E unlink(Node<E> x) {
    2. // assert x != null;
    3. final E element = x.item;
    4. final Node<E> next = x.next;
    5. final Node<E> prev = x.prev;
    6. if (prev == null) {
    7. first = next;
    8. } else {
    9. prev.next = next;
    10. x.prev = null;
    11. }
    12. if (next == null) {
    13. last = prev;
    14. } else {
    15. next.prev = prev;
    16. x.next = null;
    17. }
    18. x.item = null;
    19. size--;
    20. modCount++;
    21. return element;
    22. }

    这个方法就不做分析了,其原理就是将当前节点X的前一个节点P的next直接指向X的下一个节点D,这样X就不再关联任何引用,等待垃圾回收即可。
    这里我们同样知道,相比于ArrayList的copy数组覆盖原来节点,效率同样更高!
    到现在,我们关于链表的核心方法,增删改都分析完毕,最后介绍下它实现的队列Deque的各个方法:
    image.png
    LinkedList

  • add(E e):队尾插入新节点,如果队列空间不足,抛出异常;LinkedList没有空间限制,所以可以无限添加。
  • offer(E e):队尾插入新节点,空间不足,返回false,在LinkedList中和add方法同样效果。
  • remove():移除队头节点,如果队列为空(没有节点,first为null),抛出异常。LinkedList中就是first节点(链表头)
  • poll():同remove,不同点:队列为空,返回null
  • element():查询队头节点(不移除),如果队列为空,抛出异常。
  • peek():同element,不同点:队列为空,返回null。

总结

  1. LinkedList内部使用链表实现,相比于ArrayList更加耗费空间。
  2. LinkedList插入,删除节点不用大量copy原来元素,效率更高。
  3. LinkedList查找元素使用遍历,效率一般。
  4. LinkedList同时是双向队列的实现。

3、源码

  1. public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
  2. @java.io.Serial
  3. private static final long serialVersionUID = 876323262645176354L; // 序列化版本号
  4. transient int size = 0; // 当前元素个数0
  5. transient Node<E> first; // 指向第一个节点数据
  6. transient Node<E> last; // 指向最后一个节点
  7. /**
  8. * 存储数据的
  9. * Node数据结构
  10. */
  11. private static class Node<E> {
  12. E item;
  13. Node<E> next;
  14. Node<E> prev;
  15. Node(Node<E> prev, E element, Node<E> next) {
  16. this.item = element;
  17. this.next = next;
  18. this.prev = prev;
  19. }
  20. }
  21. /**
  22. * LinkedList构造方法
  23. */
  24. public LinkedList() {} // 构造一个空集合
  25. public LinkedList(Collection<? extends E> c) {} // 构造一个带有c集合所有元素的集合
  26. /**
  27. * 添加元素
  28. */
  29. public void addFirst(E e) {} // 将节点e加入作为第一个节点
  30. public void addLast(E e) { } // 将节点e加入作为尾节点
  31. public boolean add(E e) {} // 添加元素,会加入到链表末尾
  32. public boolean offer(E e) {} // 添加元素,调用的是add(e);就是上面一个方法
  33. public void add(int index, E element) {}// index位置加入元素element
  34. public boolean addAll(Collection<? extends E> c) {} // 添加集合元素
  35. public boolean addAll(int index, Collection<? extends E> c) {} // 指定位置后添加元素
  36. public boolean offerFirst(E e) {} // 添加e成为首元素
  37. public boolean offerLast(E e) {} // 添加e成为尾元素元素
  38. public void push(E e) {} // 添加e并成为首元素
  39. /**
  40. * 移除元素
  41. */
  42. public E removeFirst() {} // 移除首元素
  43. public E removeLast() {} // 移除最后一个节点
  44. public E pollFirst() {} // 返回并移除首元素
  45. public E pollLast() {} // 返回并移除尾元素
  46. public E remove() {} // 移除首元素
  47. public E pop() {} // 移除首元素并返回首元素
  48. public E poll() {} // 返回首元素并从链表中剔除首元素,或者返回null
  49. public boolean remove(Object o) {} // 移除元素o,如果成功返回true,失败(说明不存在)返回false
  50. public E remove(int index) {} // 移除index位置的元素
  51. public boolean removeFirstOccurrence(Object o) {} // 从头开始找元素o,并移除元素o
  52. public boolean removeLastOccurrence(Object o) {} // 从尾往前找元素o,并移除元素
  53. /**
  54. * 修改元素
  55. */
  56. public E set(int index, E element) {} // 替换index索引元素存的内容
  57. /**
  58. * 查询元素
  59. */
  60. public E getFirst() {} // 获取第一个节点
  61. public E getLast() {} // 获取最后一个节点
  62. public E peek() {} // 返回表头元素或者null
  63. public E element() {} // 返回表头元素,如果为null则抛异常,不会返回null元素
  64. public E peekLast() {} // 返回尾元素,可以返回null
  65. public E get(int index) {} // 获得index索引下的元素
  66. public int indexOf(Object o) {} // 从首元素往后找,返回元素o的索引值,如果不存在则返回-1
  67. public int lastIndexOf(Object o) {} // 从尾元素往前找,返回元素索引
  68. public boolean contains(Object o) {} // 是否包含o
  69. /**
  70. * 其它对集合操作的方法
  71. * 链表转数组
  72. * 集合大小、清空集合、克隆集合、迭代器
  73. */
  74. public Object[] toArray() {} // 链表转数组,不带泛型
  75. public <T> T[] toArray(T[] a) {} // 链表转数组,传入泛型
  76. public int size() {} // 返回链表长度
  77. public void clear() {} // 清除所有元素
  78. private LinkedList<E> superClone() {} // 克隆一个LinkedList对象
  79. public Object clone() {} // 将所有元素都克隆一份,并返回
  80. public Spliterator<E> spliterator() {} // 迭代器,允许并行处理
  81. public ListIterator<E> listIterator(int index) {} // 迭代器
  82. /**
  83. * 后面的可以不用看,主要需要掌握前面对外提供的方法
  84. *
  85. * 不对外使用的方法
  86. */
  87. E unlink(Node<E> x) {} // 移除节点x
  88. Node<E> node(int index) {} // 返回index下的节点
  89. void linkLast(E e) {} // 链接到最后一个节点上
  90. void linkBefore(E e, Node<E> succ) {} // 链接到succ节点的上一个节点
  91. private void linkFirst(E e) {} // 链接到第一个节点上
  92. private E unlinkFirst(Node<E> f) {} // 移除第一个节点
  93. private E unlinkLast(Node<E> l) {} // 移除最后一个节点
  94. private boolean isElementIndex(int index) {} // 判断index是否在链表长度内
  95. private boolean isPositionIndex(int index) {} // 判断index是否是正向索引
  96. private String outOfBoundsMsg(int index) {} // 返回超出索引范围的提示字符串:"Index: "+index+", Size: "+size;
  97. private void checkElementIndex(int index) {} // 检查元素索引index是否在链表长度以内,不是则抛异常
  98. private void checkPositionIndex(int index) {} // 检查正向索引
  99. /**
  100. * 序列化
  101. */
  102. @java.io.Serial
  103. private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {}
  104. /**
  105. * 反序列化
  106. */
  107. @SuppressWarnings("unchecked")
  108. @java.io.Serial
  109. private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {}
  110. public Iterator<E> descendingIterator() {
  111. return new DescendingIterator();
  112. }
  113. private class DescendingIterator implements Iterator<E> {
  114. private final ListItr itr = new ListItr(size());
  115. public boolean hasNext() {} // 是否有下一个元素
  116. public E next() {} // 返回下一个元素
  117. public void remove() {} // 移除当前迭代器返回的元素
  118. }
  119. private class ListItr implements ListIterator<E> {
  120. private Node<E> lastReturned; // 上一个元素节点
  121. private Node<E> next; // 下一个节点元素
  122. private int nextIndex; // 上一个元素索引值
  123. private int expectedModCount = modCount;// 并发修改异常计数
  124. ListItr(int index) {} // 构造器
  125. public void add(E e) {} // 添加
  126. public void remove() {} // 删除
  127. public void set(E e) {} // 修改
  128. public boolean hasNext() {} // 判断是否有下一个元素
  129. public E next() {} // 返回下一个元素
  130. public boolean hasPrevious() {} // 是否有上一个值
  131. public E previous() {} // 返回上一个元素
  132. public int nextIndex() {} // 下一个元素的索引值
  133. public int previousIndex() {} // 上一个元素的索引值
  134. public void forEachRemaining(Consumer<? super E> action) {} // 遍历迭代器剩余元素
  135. final void checkForComodification() {} // 检查并发修改异常
  136. }
  137. static final class LLSpliterator<E> implements Spliterator<E> {
  138. static final int BATCH_UNIT = 1 << 10; // batch array size increment
  139. static final int MAX_BATCH = 1 << 25; // max batch array size;
  140. final LinkedList<E> list; // null OK unless traversed
  141. Node<E> current; // current node; null until initialized
  142. int est; // size estimate; -1 until first needed
  143. int expectedModCount; // initialized when est set
  144. int batch; // batch size for splits
  145. LLSpliterator(LinkedList<E> list, int est, int expectedModCount) {} // 构造方法
  146. public void forEachRemaining(Consumer<? super E> action) {} // 遍历剩余元素
  147. public boolean tryAdvance(Consumer<? super E> action) {} // 尝试获取剩余元素,如果没有剩余元素返回false
  148. final int getEst() {}
  149. public long estimateSize() {}
  150. public Spliterator<E> trySplit() {}
  151. public int characteristics() {}
  152. }
  153. }

4、双向链表

另外推荐一篇把双向链表讲清楚的文章:https://juejin.cn/post/6844903648154271757

双向链表: 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
image.png

双向循环链表: 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。
image.png