图片.png

继承了AbstractSequentialList抽象类:在遍历LinkedList的时候,官方更推荐使用顺序访问,也就是使用我们的迭代器。(因为LinkedList底层是通过一个链表来实现的)(虽然LinkedList也提供了get(int index)方法,但是底层的实现是:每次调用get(int index)方法的时候,都需要从链表的头部或者尾部进行遍历,每一的遍历时间复杂度是O(index),而相对比ArrayList的底层实现,每次遍历的时间复杂度都是O(1)。所以不推荐通过get(int index)遍历LinkedList。至于上面的说从链表的头部后尾部进行遍历:官方源码对遍历进行了优化:通过判断索引index更靠近链表的头部还是尾部来选择遍历的方向)(所以这里遍历LinkedList推荐使用迭代器)。
实现了List接口。(提供List接口中所有方法的实现)
实现了Cloneable接口,它支持克隆(浅克隆),底层实现:LinkedList节点并没有被克隆,只是通过Object的clone()方法得到的Object对象强制转化为了LinkedList,然后把它内部的实例域都置空,然后把被拷贝的LinkedList节点中的每一个值都拷贝到clone中。(后面有源码解析)
实现了Deque接口。实现了Deque所有的可选的操作。

实现了Serializable接口。表明它支持序列化。(和ArrayList一样,底层都提供了两个方法:readObject(ObjectInputStream o)、writeObject(ObjectOutputStream o),用于实现序列化,底层只序列化节点的个数和节点的值)。

首先我们先看看LinkedList的核心,也就是LinkedList中真正用来存储元素的数据结构。

Node

Node 类是LinkedList中的私有内部类,LinkedList中就是通过Node来存储集合中的元素。
E :节点的值。
Node next:当前节点的后一个节点的引用(可以理解为指向当前节点的后一个节点的指针)
Node prev:当前节点的前一个节点的引用(可以理解为指向当前节点的前一个节点的指针)

  1. private static class Node<E> {
  2. E item;
  3. Node<E> next;
  4. Node<E> prev;
  5. Node(Node<E> prev, E element, Node<E> next) {
  6. this.item = element;
  7. this.next = next;
  8. this.prev = prev;
  9. }
  10. }

属性

size:用来记录LinkedList的大小
Node first:用来表示LinkedList的头节点。
Node last:用来表示LinkedList的尾节点

  1. transient int size;
  2. transient LinkedList.Node<E> first;
  3. transient LinkedList.Node<E> last;

图片.png

构造器

LinkedList提供了两个构造器,ArrayList比它多提供了一个通过设置初始化容量来初始化类。LinkedList不提供该方法的原因:因为LinkedList底层是通过链表实现的,每当有新元素添加进来的时候,都是通过链接新的节点实现的,也就是说它的容量是随着元素的个数的变化而动态变化的。而ArrayList底层是通过数组来存储新添加的元素的,所以我们可以为ArrayList设置初始容量(实际设置的数组的大小)。
①空构造器:

  1. public LinkedList() {
  2. }

addall

  1. // 首先调用一下空的构造器。
  2. //然后调用addAll(c)方法。
  3. public LinkedList(Collection<? extends E> c) {
  4. this();
  5. addAll(c);
  6. }
  7. //通过调用addAll(int index, Collection<? extends E> c) 完成集合的添加。
  8. public boolean addAll(Collection<? extends E> c) {
  9. return addAll(size, c);
  10. }
  11. //重点来了。(还是建议大家自己手动写一次源码实现。)
  12. //几乎所有的涉及到在指定位置添加或者删除或修改操作都需要判断传进来的参数是否合法。
  13. // checkPositionIndex(index)方法就起这个作用。该方法后面会介绍。
  14. public boolean addAll(int index, Collection<? extends E> c) {
  15. checkPositionIndex(index);
  16. //先把集合转化为数组,然后为该数组添加一个新的引用(Objext[] a)。
  17. Object[] a = c.toArray();
  18. //新建一个变量存储数组的长度。
  19. int numNew = a.length;
  20. //如果待添加的集合为空,直接返回,无需进行后面的步骤。后面都是用来把集合中的元素添加到
  21. //LinkedList中。
  22. if (numNew == 0)
  23. return false;
  24. //这里定义了两个节点:(读这里的程序的时候,强烈建议自己画一个图,辅助你理解这个过程)。
  25. //Node<E> succ:指代待添加节点的位置。
  26. //Node<E> pred:指代待添加节点的前一个节点。
  27. //下面的代码是依据新添加的元素的位置分为两个分支:
  28. //①新添加的元素的位置位于LinkedList最后一个元素的后面。
  29. //新添加的元素的位置位于LinkedList中。
  30. //如果index==size;说明此时需要添加LinkedList中的集合中的每一个元素都是在LinkedList
  31. //最后面。所以把succ设置为空,pred指向尾节点。
  32. //否则的话succ指向插入待插入位置的节点。这里用到了node(int index)方法,这个方法
  33. //后面会详细分析,这里只需要知道该方法返回对应索引位置上的Node(节点)。pred指向succ节点的前一个节点。
  34. //上面分析的这几步如果看不懂的话,可以自己画个图,清晰明了^_^。
  35. Node<E> pred, succ;
  36. if (index == size) {
  37. succ = null;
  38. pred = last;
  39. } else {
  40. succ = node(index);
  41. pred = succ.prev;
  42. }
  43. //接着遍历数组中的每个元素。在每次遍历的时候,都新建一个节点,该节点的值存储数组a中遍历
  44. //的值,该节点的prev用来存储pred节点,next设置为空。接着判断一下该节点的前一个节点是否为
  45. //空,如果为空的话,则把当前节点设置为头节点。否则的话就把当前节点的前一个节点的next值
  46. //设置为当前节点。最后把pred指向当前节点,以便后续新节点的添加。
  47. for (Object o : a) {
  48. @SuppressWarnings("unchecked") E e = (E) o;
  49. Node<E> newNode = new Node<>(pred, e, null);
  50. if (pred == null)
  51. first = newNode;
  52. else
  53. pred.next = newNode;
  54. pred = newNode;
  55. }
  56. //这里仍然和上面一样,分两种情况对待:
  57. //①当succ==null(也就是新添加的节点位于LinkedList集合的最后一个元素的后面),
  58. //通过遍历上面的a的所有元素,此时pred指向的是LinkedList中的最后一个元素,所以把
  59. //last指向pred指向的节点。
  60. //当不为空的时候,表明在LinkedList集合中添加的元素,需要把pred的next指向succ上,
  61. //succ的prev指向pred。
  62. //最后把集合的大小设置为新的大小。
  63. //modCount(修改的次数)自增。
  64. if (succ == null) {
  65. last = pred;
  66. } else {
  67. pred.next = succ;
  68. succ.prev = pred;
  69. }
  70. size += numNew;
  71. modCount++;
  72. return true;
  73. }

增加

函数

函数 说明
boolean add(E e) 在链表尾部添加一个元素,如果成功,返回true,否则返回false。
void addFirst(E e) 在链表头部插入一个元素。
addLast(E e) 在链表尾部添加一个元素。
void add(int index, E element) 在指定位置插入一个元素。


源码

把参数中的元素作为链表的第一个元素。

  1. //因为我们需要把该元素设置为头节点,所以需要新建一个变量把头节点存储起来。
  2. //然后新建一个节点,把next指向f,然后自身设置为头结点。
  3. //再判断一下f是否为空,如果为空的话,说明原来的LinkedList为空,所以同时也需要把新节点设
  4. //置为尾节点。否则就把f的prev设置为newNode。
  5. //size和modCount自增。
  6. private void linkFirst(E e) {
  7. final Node<E> f = first;
  8. final Node<E> newNode = new Node<>(null, e, f);
  9. first = newNode;
  10. if (f == null)
  11. last = newNode;
  12. else
  13. f.prev = newNode;
  14. size++;
  15. modCount++;
  16. }

添加尾元素

  1. //因为我们需要把该元素设置为尾节点,所以需要新建一个变量把尾节点存储起来。
  2. //然后新建一个节点,把last指向l,然后自身设置为尾结点。
  3. //再判断一下l是否为空,如果为空的话,说明原来的LinkedList为空,所以同时也需要把新节点设
  4. //置为头节点。否则就把l的next设置为newNode。
  5. //size和modCount自增。
  6. void linkLast(E e) {
  7. final Node<E> l = last;
  8. final Node<E> newNode = new Node<>(l, e, null);
  9. last = newNode;
  10. if (l == null)
  11. first = newNode;
  12. else
  13. l.next = newNode;
  14. size++;
  15. modCount++;
  16. }

指定值之前插入元素

  1. void linkBefore(E e, Node<E> succ) {
  2. // assert succ != null;
  3. //首先我们需要新建一个变量指向succ节点的前一个节点,因为我们要在succ前面插入一个节点。
  4. //接着新建一个节点,它的prev设置为我们刚才新建的变量,后置节点设置为succ。
  5. //然后修改succ的prev为新节点。
  6. //接着判断一个succ的前一个节点是否为空,如果为空的话,需要把新节点设置为为头结点。
  7. //如果不为空,则把succ的前一个节点的next设置为新节点。
  8. final Node<E> pred = succ.prev;
  9. final Node<E> newNode = new Node<>(pred, e, succ);
  10. succ.prev = newNode;
  11. if (pred == null)
  12. first = newNode;
  13. else
  14. pred.next = newNode;
  15. size++;
  16. modCount++;
  17. }

删除

函数

函数 说明
boolean remove(Object o) 从当前链表中移除指定的元素。
E remove(int index) 从当前链表中移除指定位置的元素。
E removeFirst() 从当前链表中移除第一个元素。
E removeLast() 从当前链表中移除最后一个元素。
E remove() 从当前链表中移除第一个元素,同removeLast()相同。


源码

删除头结点

  1. private E unlinkFirst(Node<E> f) {
  2. // assert f == first && f != null;
  3. //定义一个变量element指向待删除节点的值,接着定义一个变量next指向待删除节点的
  4. //下一个节点。(因为我们需要设置f节点的下一个节点为头结点,而且需要把f节点的值设置为空)
  5. //接着把f的值和它的next设置为空,把它的下一个节点设置为头节点。
  6. //接着判断一个它的下一个节点是否为空,如果为空的话,则需要把last设置为空。否则
  7. //的话,需要把next的prev设置为空,因为next现在指代头节点。
  8. final E element = f.item;
  9. final Node<E> next = f.next;
  10. f.item = null;
  11. f.next = null; // help GC
  12. first = next;
  13. if (next == null)
  14. last = null;
  15. else
  16. next.prev = null;
  17. size--;
  18. modCount++;
  19. return element;
  20. }

删除末尾节点

  1. rivate E unlinkLast(Node<E> l) {
  2. // assert l == last && l != null;
  3. final E element = l.item;
  4. final Node<E> prev = l.prev;
  5. l.item = null;
  6. l.prev = null; // help GC
  7. last = prev;
  8. if (prev == null)
  9. first = null;
  10. else
  11. prev.next = null;
  12. size--;
  13. modCount++;
  14. return element;
  15. }

变量用来存储当前被删除节点的值,因为我们最后需要把这个返回回去。第二个变量用来存储待删除节点的前一个节点,第三个变量用来存储待删除节点的后一个节点。
接下来判断一下,待删除节点的前一个节点是否为空,如果为空的话,表明待删除的节点是我们的头结点。则需要把待删除节点的后一个节点设置为头结点。如果不为空,就需要把待删除的节点的前、后节点链接起来,此刻只需要链接一部分,通过pre.next=next;x.prev= null设置(这里也是JDK设计的技巧的部分,一般我们都会直接在这里把链接全部设置上),接着判断一下待删除的节点是否为空,如果为空的话,则表明待删除节点是尾节点,所以需要我们把待删除节点的前一个节点设置为尾节点。如果不为空的,则把next.prev=prev,x.next=null。最后把待删除节点的值设置为空。把size和modCount自减,最后返回被删除节点的值

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

查找

方法

E get(int index) 从当前链表中获取指定位置的元素。
E getFirst() 从当前链表中获取第一个元素。
E getLast() 从当前链表中获取最后一个元素。


源码

先比较一下index更靠近链表(LinkedList)的头节点还是尾节点。然后进行遍历,获取相应的节点。

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