1.LinkedList实现的基本原理

LinkedList是一个双向链表,它主要有两个表示头尾节点的成员变量first  、last,因其有头尾两个节点,所以从头或从尾操作数据都非常容易快捷。LinkedList通过内部类Node来保存元素 ,一个Node对象表示链表的一个节点,有多少个元素就需要多少个Node节点。如果要添加元素,则新建一个Node节点,保存这个元素,同时指定其前驱节点和后继节点的引用。若要删除一个元素,则将取消此元素对应的Node节点在链表中的前驱后继关系。

2.ListedList实现的关键 -——静态内部类Node

Node类有3个成员变量:

1)表示当前节点保存的元素item ,

2)表示后继节点的引用next

3)表示前驱节点的引用 prev

  1. private static class Node<E> {
  2. E item; //当前节点保存的元素
  3. Node<E> next; //后继节点的引用
  4. Node<E> prev; //前驱节点的引用
  5.  
  6. Node(Node<E> prev, E element, Node<E> next) {
  7. this.item = element;
  8. this.next = next;
  9. this.prev = prev;
  10. }
  11. }

Node类有这3个成员能够很清楚的表示自己的逻辑结构。首先它能确定自己的位置,属性prev 、next分别指定了前驱、后继节点分别是谁,指明了当前节点在链表结构中的位置;另外成员变量item明确了当前节点要保存的元素。

3.ListedList的成员变量

其成员变量主要是表示链表头 尾节点的两个成员变量:头节点first 、  尾节点last。

因为有头尾两个节点,可以很方便地从头部或从尾部这两个方向进行逻辑操作。

只用一个头节点可实现单向链表,它的效率不高,因为只能从单向从头到尾遍历,不能根据实际情况选择更优的遍历方向。

  1. // 链表的所含元素个数
  2. transient int size = 0;
  3.  
  4. // 链表的头节点
  5. transient Node<E> first;
  6. // 链表的尾节点
  7. transient Node<E> last;
  8. /*
  9. * 此属性在本类中没有,此属性是从父类AbstractSequentialList中继承而来
  10. * 链表结构被修改的次数,防止在迭代遍历过程中,修改链表结构
  11. */
  12. protected int modCount = 0;

4 .其构造方法

构造方法有两个,一个默认构造方法,将初始化一个空链表;另一个带单参数的构造方法,将初始化后,此链表将包含集合参数c的所有元素

  1. public LinkedList() { } //头尾节点都是null的空链表
  2.  
  3. //初始化后,包含集合c所有元素的链表
  4. public LinkedList(Collection<? extends E> c) {
  5. this();
  6. addAll(c);//添加集合c所有元素
  7. }

5.其常用的API

1). public boolean add(E e)   在尾部添加一个元素

此方法去调用了一个对链表尾部连接新元素的方法linkLast(E)

  1. public boolean add(E e) {
  2. linkLast(e);//在尾部链接一个新元素
  3. return true;
  4. }

此时去看看linkLast(E)的实现细节:

  ------- 在添加新元素时要注意特殊情况,即要考虑当前的链表为空、不含任何元素的可能,此时需要将当前新添加的节点设为头节点

  1. void linkLast(E e) {
  2. final Node<E> l = last; // 暂存链表的原尾节点
  3. /*
  4. * 要链接的新节点
  5. * 在尾部添加节点,新添加的节点即为链表更新后的尾节点,其前驱为原尾节点,后继节点为空
  6. */
  7. final Node<E> newNode = new Node<>(l, e, null);
  8. //更新后的尾节点是此时新添加的节点
  9. last = newNode;
  10. if (l == null)
  11. /*
  12. * 原尾节点为空,表明在添加当前元素之前链表为空,
  13. * 那么添加一个元素后,头节点 、尾节点是同一个节点,即为当前新添加的节点
  14. */
  15. first = newNode;
  16. else
  17. // 更新后,原尾节点的后继节点就是新添加的节点
  18. l.next = newNode;
  19. size++;
  20. modCount++;//添加一个元素,链表结构被修改一次,modCount自加一
  21. }

2)public void add(int index, E element)  在指定下标处添加元素

此方法有两个分支,若插入的下标位置等于链表元素个数,则可直接在链表尾部添加要插入的元素;而其它一般情况则调用linkBefore(E,Node)方法,在指定某节点之前插入指定元素。

而linkBefore(E,Node)的方法参数又调用了Node<E>  node(int)方法。

  1. public void add(int index, E element) {
  2. checkPositionIndex(index); //检查下标是否合法
  3.  
  4. if (index == size) //插入的位置index=size即可直接在链表尾部添加元素
  5. linkLast(element);
  6. else
  7. //在指定的节点之前插入元素
  8. linkBefore(element, node(index));
  9. }

先来看看node(int)方法是如何根据下标查找到下标对应的Node节点

  ------- node(int)方法中有两个分支,根据index是否小于size的一半作为进入不同分支的条件。此分支条件是用来确定待查找的节点与头/尾节点的距离远近,若待查找的节点和头节点较近,就从头节点开始查找;若待查找的节点与尾节点较近,则从尾节点开始遍历查找。这主要是从遍历效率角度考虑的。

  1. Node<E> node(int index) {
  2. //index作为循环遍历停止的条件
  3. if (index < (size >> 1)) { // 如果index小于size的一半,则从头部开始向后查找
  4. Node<E> x = first;
  5. for (int i = 0; i < index; i++)
  6. x = x.next; //将当前节点的后继节点赋值给当前节点
  7. return x;
  8. } else { // 如果index大于size的一半,则从尾部开始向前查找
  9. Node<E> x = last;
  10. for (int i = size - 1; i > index; i--)
  11. x = x.prev;//将当前节点的前驱节点赋值给当前节点
  12. return x;
  13. }
  14. }

最后来看linkBefore(E,Node)如何在指定的节点前添加指定的元素

  -------其中也对插入位置的进行了判断,如果是在头部插入节点,则需要将头节点的引用更新为待插入节点。

  1. void linkBefore(E e, Node<E> succ) {
  2. //插入点节点succ的前驱节点pred
  3. final Node<E> pred = succ.prev;
  4. /*
  5. * 待插入的节点,
  6. * 此节点将取代插入点节点succ,插入点succ将后移一位
  7. * 此节点的前驱是插入点节点的前驱节点pred,其后继节点就是插入点节点succ
  8. */
  9. final Node<E> newNode = new Node<>(pred, e, succ);
  10. //插入点节点的前驱是待插入节点
  11. succ.prev = newNode;
  12.  
  13. if (pred == null)
  14. /*
  15. * 插入点节点的前驱节点pred为为空,表明此时在头部直接插入节点
  16. * 那么链表更新后,当前的待插入节点就是头节点
  17. */
  18. first = newNode;
  19. else
  20. /*
  21. * 插入点节点的前驱节点pred的后继节点是待插入节点
  22. */
  23. pred.next = newNode;
  24. size++;
  25. modCount++;
  26. }

3) public E remove(int index)   移除指定位置的元素

先利用node(int)方法,根据下标查找到对应的Node节点,再调用unlink(Node)方法,取消指定节点在链表中的链接关系。

  1. public E remove(int index) {
  2. checkElementIndex(index);//检查下标是否合法
  3. /*
  4. * 利用node(int)方法,根据下标查找到对应的Node节点
  5. * 再调用unlink(Node)方法,取消指定节点在链表中的链接关系
  6. */
  7. return unlink(node(index));
  8. }

node(int)方法已经在之前已经分析过了,这里主要来看unlink(Node)方法的实现

   ------unlink(Node)方法比较复杂,但是将思路理清楚,也就不难理解了。

(1)因为待移除节点x将被删除,x的前驱prev和x的后继next都将不再依赖x这个中间连接者,所以要将前驱节点prev与后继节点next直接链接起来。LinkedList是双向链表,每个节点都要双向(前后)维护链接关系,即同时维护前驱与后继。那么prev、next两者都要更新链接关系,链表更新后prev的后继节点是next,即prev.next=next, 而next的前前驱节点又是prev,即next.prev=prev 。

(2)另外还要考虑待移除节点x是头节点或尾节点这种特殊情况(first=next或last=prev)。

(3)因为待移除节点x不会再被使用了,最后还要将待移除节点x的各个属性赋为null,便于GC回收内存资源(x.item=null ;x.prev=null;x.next=null)。

  1. E unlink(Node<E> x) {
  2. //保存待移除节点储存的元素,最后要返回这个被移除的元素
  3. final E element = x.item;
  4. //待移除节点的后继节点
  5. final Node<E> next = x.next;
  6. //待移除节点的前驱节点
  7. final Node<E> prev = x.prev;
  8.  
  9. /**
  10. * 维护待移除节点的前节点驱prev的链接关系
  11. */
  12. if (prev == null) {
  13. /*
  14. * 待移除节点的前驱prev为空,表明待移除节点是头节点
  15. * 当待移除节点是头节点时,移除元素更新后,待移除节点的后继节点next就是链表更新后的头节点
  16. * 此时待移除节点的前驱节点本来就为空,不用再去赋为空值
  17. */
  18. first = next;
  19. } else {
  20. /* 在其他一般情况下(非头节点),更新后
  21. * 待移除节点的前驱节点prev的后继节点 就是待移除节点的后继节点next
  22. * 将待移除节点x的属性prev赋为空,便于最后的垃圾回收
  23. */
  24. prev.next = next;
  25. x.prev = null;
  26. }
  27.  
  28. /*
  29. * 维护待移除节点的后继节点的链接关系
  30. */
  31. if (next == null) {
  32. /*
  33. * 待移除节点的后继next为空,表明待移除节点是尾节点
  34. * 当待移除节点是尾节点,移除元素更新后,待移除节点的前驱节点prev就是链表更新后的尾节点
  35. * 此时待移除节点的后继节点本来就为空,不用再去赋为空值
  36. */
  37. last = prev;
  38. } else {
  39. /* 在其他一般情况下(非尾节点),移除更新后
  40. * 待移除节点的后继节点next的前驱节点 就是的待移除节点的前驱节点prev
  41. * 将待移除节点x的属性next赋为空,便于最后的垃圾回收
  42. */
  43. next.prev = prev;
  44. x.next = null;
  45. }
  46.  
  47. x.item = null; //将待移除节点储存的元素引用赋空
  48. size--;
  49. modCount++;//链表结构被修改,modCount自加
  50. return element;//返回被移除元素
  51. }

4) public E remove(Object o)  根据元素的引用移除元素

根据元素是否为空进入不同的分支。从头元素开始向后遍历,若链表中的一个元素与o相等,则调用unlink(Node)方法移除当前节点,退出循环并返回true;若遍历了所有元素,没有任何一个元素与o相等,则返回false,移除指定元素失败。

  1. public boolean remove(Object o) {
  2. if (o == null) { //待移除元素为空
  3. for (Node<E> x = first; x != null; x = x.next) {
  4. if (x.item == null) {
  5. unlink(x);
  6. return true;
  7. }
  8. }
  9. } else { //待移除元素不为空
  10. for (Node<E> x = first; x != null; x = x.next) {
  11. if (o.equals(x.item)) {
  12. unlink(x);
  13. return true;
  14. }
  15. }
  16. }
  17. return false;
  18. }

5)public E set(int index, E element)  替换指定下标对应的元素

(1)找到下标index对应的节点

(2)保存index下标位置的原元素值

(3) 将节点的item属性更新为element

  1. public E set(int index, E element) {
  2. checkElementIndex(index);//检查下标
  3. Node<E> x = node(index); //查找下标index对应的节点
  4. E oldVal = x.item; //保存原值
  5. x.item = element;//将下标index对应节点所保存的元素值更新
  6. return oldVal;
  7. }

6)public  E get(int index)  获取指定下标对应的元素

找到下标index对应的节点,返回此节点的item属性。

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

7)  public int indexOf(Object o)  根据元素值确定其第一次出现的位置下标

从头节点开始向后遍历,若找到这个元素则退出循环并返回当前遍历的下标,若不存在此元素返回-1.

  1. public int indexOf(Object o) {
  2. int index = 0;
  3. if (o == null) {
  4. for (Node<E> x = first; x != null; x = x.next) {
  5. if (x.item == null)
  6. return index;
  7. index++;
  8. }
  9. } else {
  10. for (Node<E> x = first; x != null; x = x.next) {
  11. if (o.equals(x.item))
  12. return index;
  13. index++;
  14. }
  15. }
  16. return -1;
  17. }

8)  public int lastIndexOf(Object o)  根据元素值确定其最后一次出现的位置下标

与“public int indexOf(Object o)”的实现原理基本一致,只有遍历的方向不同而已,此方法是从尾节点向前遍历

  1. public int lastIndexOf(Object o) {
  2. int index = size;
  3. if (o == null) {
  4. for (Node<E> x = last; x != null; x = x.prev) {
  5. index--;
  6. if (x.item == null)
  7. return index;
  8. }
  9. } else {
  10. for (Node<E> x = last; x != null; x = x.prev) {
  11. index--;
  12. if (o.equals(x.item))
  13. return index;
  14. }
  15. }
  16. return -1;
  17. }

9)public void clear()  清空所有元素

  1. public void clear() {
  2. /*
  3. * 将所有结点的所有属性赋空
  4. */
  5. for (Node<E> x = first; x != null; ) {
  6. //保存遍历时的当前节点的后继
  7. Node<E> next = x.next;
  8. x.item = null;
  9. x.next = null;
  10. x.prev = null;
  11. x = next; //将后继节点赋给当前节点
  12. }
  13. first = last = null;//头、尾节点同时赋为空,表明这是一个空链表
  14. size = 0;//不含任何元素,size设为0
  15. modCount++;
  16. }

10)  public boolean addAll(int index, Collection<? extends E> c)  在指定下标处插入所有集合元素

此方法也对一些特殊情况做了判断分支处理,如在链表的尾部添加元素,或是在链表的头部添加元素等。

  1. public boolean addAll(int index, Collection<? extends E> c) {
  2. checkPositionIndex(index);//检查下标
  3. Object[] a = c.toArray();
  4. int numNew = a.length;
  5. if (numNew == 0) //集合c没有任何元素,不用添加任何一个元素,直接返回false。
  6. return false;
  7. //pred表示插入点原节点的前驱节点, succ表示插入点的原节点,
  8. Node<E> pred, succ;
  9. if (index == size) {
  10. /*
  11. * index=size表明在链表的尾部批量添加元素
  12. * 前驱pred为尾节点
  13. * 插入点的原节点succ不存在,则为null
  14. */
  15. succ = null;
  16. pred = last;
  17. } else {
  18. /*
  19. * 非尾部添加元素
  20. */
  21. succ = node(index);
  22. pred = succ.prev;
  23. }
  24. /*
  25. * 将集合中的所有元素加入到链表中
  26. */
  27. for (Object o : a) {
  28. @SuppressWarnings("unchecked") E e = (E) o;
  29. //当前要添加的节点(初始化了新节点的链接关系)
  30. Node<E> newNode = new Node<>(pred, e, null);
  31. //更新前驱元素的链接关系
  32. if (pred == null)
  33. //在头部添加节点时
  34. first = newNode;
  35. else
  36. //非头部添加节点
  37. pred.next = newNode;
  38. //当前新添加节点作为下次遍历时新添加节点的前驱
  39. pred = newNode;
  40. }
  41.  
  42. /**
  43. * 维护添加最后一个元素时的链接关系
  44. */
  45. if (succ == null) {
  46. /*
  47. * 在尾部添加时,最后添加的那个元素即为尾节点
  48. */
  49. last = pred;
  50. } else {
  51. /*
  52. * 非尾部添加时
  53. */
  54. pred.next = succ;
  55. succ.prev = pred;
  56. }
  57.  
  58. size += numNew;
  59. modCount++;
  60. return true;
  61. }

6.总结源码经验,仿写双向链表

一个集合的基本功能是增删改查,总结一下这些功能的大致思路:

1)”增“元素: 可以在链表的尾部增加元素或在链表的中部插入元素。在尾部添加元素需要维护原尾部节点和新添加节点的相互链接关系;而在中部插入新元素需要维护新插入节点、插入点原节点、插入入节点前驱节点 这三个节点间的相互链接关系。可以统一起来看,"增"元素都是插入元素,需要维护新插入节点、新插入节点的前/后节点间的相互链接关系(在尾部添加元素时,其后继节点为空而已)。

2)“删”元素:维护待删节点的前后节点的相互链接关系,即将前后节点直接链接起来,另外还要将待删节点的各属性赋空(利于垃圾回收)。同时得考虑特殊情况下——在头部或尾部删除节点,此时还要更新头节点或尾节点的引用。

3)“改”元素:先根据索引或元素值找到对应的Node节点,再重设该节点的item属性。

4)“查”元素:根据索引,循环遍历找到对应的Node节点,获取该节点的item属性。

和LinkedList相似的双向链表:

  1. package collection;
  2.  
  3. import java.lang.reflect.Array;
  4. import java.util.Collection;
  5. import java.util.Iterator;
  6. import java.util.List;
  7. import java.util.ListIterator;
  8. import java.util.NoSuchElementException;
  9.  
  10. public class LinkList<E> implements Iterable<E>, List<E> {
  11. // 链表的所含元素个数
  12. private int size = 0;
  13. // 链表的头节点
  14. private Node<E> head = null;
  15. // 链表的尾节点
  16. private Node<E> tail = null;
  17. /*
  18. * 链表结构被修改的次数,防止在迭代遍历过程中,修改链表结构
  19. */
  20. int modCount = 0;
  21.  
  22. /**
  23. * 添加一个元素
  24. *
  25. * @param e
  26. * @return
  27. */
  28. public boolean add(E e) {
  29. return linkTail(e);
  30. }
  31.  
  32. /**
  33. * 在链表尾部新链接一个元素
  34. *
  35. * @param e
  36. * @return
  37. */
  38. private boolean linkTail(E e) {
  39. final Node<E> oldTail = tail;
  40.  
  41. final Node<E> newNode = new Node<>(oldTail, e, null);
  42. tail = newNode;
  43. if (oldTail == null)
  44. head = newNode;
  45. else {
  46. oldTail.next = newNode;
  47. }
  48.  
  49. modCount++;
  50. size++;
  51. return true;
  52. }
  53.  
  54. /**
  55. * 移除指定下标处的元素
  56. *
  57. * @param index
  58. * @return
  59. */
  60. public E remove(int index) {
  61. checkElementIndex(index);
  62. Node<E> nodeRemove = fastNodeAt(index);// 快速查找到指定下标对应的节点
  63. return unlink(nodeRemove); // 取消这个节点在链表结构中链接
  64. }
  65.  
  66. /**
  67. * 根据下标快速查找节点
  68. */
  69. private Node<E> fastNodeAt(int index) {
  70. if (index < size >> 1) {// 如果index小于size的一半,则从头部开始查找
  71. Node<E> current = head;
  72.  
  73. for (int i = 0; i < index; i++)
  74. current = current.next;
  75. return current;
  76.  
  77. } else {// 如果index大于size的一半,则从尾部开始查找
  78. Node<E> current = tail;
  79. for (int i = size - 1; i > index; i--)
  80. current = current.prev;
  81. return current;
  82.  
  83. }
  84. }
  85.  
  86. /**
  87. * 取消指定节点在链表结构中的链接
  88. */
  89. private E unlink(Node<E> node) {
  90.  
  91. Node<E> prev = node.prev;
  92. Node<E> next = node.next;
  93. E oldData = node.data;
  94.  
  95. /*
  96. * 维护被移除节点的前驱节点的链接关系
  97. */
  98. if (prev == null) {
  99. /**
  100. * 当被移除节点是头节点时,更新后的头节点就是被移除节点的下一节点
  101. * 此时被移除节点的前节点本来就为空,不用再去赋为空值
  102. */
  103. head = next;
  104. } else {
  105. prev.next = next;// 更新后,被移除节点的前节点对应的 后节点是 被移除节点的后节点
  106. // 被移除节点的前节点赋空,若不赋空则一直存在对前置节点的引用,可能出现内存泄漏
  107. node.prev = null;
  108. }
  109. /*
  110. * 维护被移除节点的后继节点的链接关系
  111. */
  112. if (next == null) {
  113. /**
  114. * 当被移除节点是尾节点时,更新后的尾节点就是被移除节点的前驱节点
  115. * 此时被移除节点的后继节点本来就为空,不用再去赋为空值
  116. */
  117. tail = prev;
  118. } else {
  119. // 更新后,被移除节点的后继接节点对应的前驱结点 就被移除节点的前驱结点
  120. next.prev = prev;
  121. node.next = null;
  122. }
  123. node.data = null;
  124. // node=null;
  125. size--;
  126. modCount++;
  127. return oldData;
  128. }
  129.  
  130. /**
  131. * 更新指定下标处的元素,
  132. *
  133. * @param index
  134. * @param e
  135. * @return 原元素值
  136. */
  137. public E set(int index, E e) {
  138. checkElementIndex(index);// 快速查找到指定下标对应的节点
  139. Node<E> node = fastNodeAt(index);
  140. E oldData = node.data; // 保存老值
  141. node.data = e;
  142. return oldData;
  143. }
  144.  
  145. /**
  146. * 获取指定下标的元素
  147. *
  148. * @param index
  149. * @return
  150. */
  151. public E get(int index) {
  152. checkElementIndex(index);
  153. return fastNodeAt(index).data;
  154. }
  155.  
  156. /**
  157. * 指定元素第一次出现的下标位置
  158. *
  159. * @param obj
  160. * @return
  161. */
  162. public int indexOf(Object obj) {
  163. if (size == 0)// 不含任何元素,返回-1
  164. return -1;
  165. int index = 0;
  166. E curElement;
  167. for (Node<E> next = head; next != null; next = next.next) {// 从头节点开始遍历
  168. curElement = next.data;
  169. if (curElement == obj || (curElement != null && curElement.equals(obj)))
  170. // 找到了对应的元素,返回当前下标
  171. return index;
  172. index++;
  173. }
  174. return -1;// 没找到对应的元素
  175. }
  176.  
  177. public Node<E> nodeOf(Object obj) {
  178. if (size == 0)
  179. return null;
  180. E curElement;
  181. for (Node<E> next = head; next != null; next = next.next) {// 从头节点开始遍历
  182. curElement = next.data;
  183. if (curElement == obj || (curElement != null && curElement.equals(obj))) {
  184. return next;
  185.  
  186. }
  187. }
  188. return null;
  189. }
  190.  
  191. /**
  192. * 返回指定元素最后一次出现的下标位置
  193. *
  194. * @param obj
  195. * @return
  196. */
  197. public int lastIndexOf(Object obj) {
  198. if (size == 0)// 不含任何元素,返回-1
  199. return -1;
  200. E curElement;
  201. int index = 0;
  202. for (Node<E> prev = tail; prev != null; prev = prev.prev) {// 从尾节点开始遍历
  203. curElement = prev.data;
  204. if (curElement == obj || (curElement != null && curElement.equals(obj)))
  205. return index;
  206. index++;
  207. }
  208. return -1;
  209. }
  210.  
  211. public boolean remove(Object obj) {
  212. if (size == 0)// 不含任何元素,false
  213. return false;
  214. E curElement;
  215. for (Node<E> next = head; next != null; next = next.next) {// 从头节点开始遍历
  216. curElement = next.data;
  217. if (curElement == obj || (curElement != null && curElement.equals(obj))) {
  218. unlink(next);
  219. return true;
  220. }
  221. }
  222. return false;
  223. }
  224.  
  225. public int size() {
  226. return size;
  227. }
  228.  
  229. public boolean isEmpty() {
  230. return size == 0;
  231. }
  232.  
  233. public void add(int index, E e) {
  234.  
  235. checkElementPosition(index);
  236. if (index == size) {
  237. linkTail(e);
  238. } else {
  239. linkBefore(index, e);
  240. }
  241. }
  242.  
  243. private boolean linkBefore(int index, E e) {
  244. Node<E> node = fastNodeAt(index);
  245. Node<E> prev = node.prev;
  246. Node<E> newNode = new Node<>(prev, e, node);
  247.  
  248. if (prev == null) {
  249. head = newNode;
  250.  
  251. } else {
  252. prev.next = newNode;
  253.  
  254. }
  255. node.prev = newNode;
  256. size++;
  257. modCount++;
  258. return true;
  259. }
  260.  
  261. // private Node<E> nodeAt(int index) {
  262. // Node<E> targetNode = null;
  263. // if (index < (size >> 1)) {// 如果index小于size的一半,则从头部开始查找这个元素
  264. // Node<E> next = null;
  265. // int i = 0;
  266. // for (Node<E> current = head; current != null;) {
  267. // if (i == index) {
  268. // targetNode = current;
  269. // break;
  270. // }
  271. // next = current.next;
  272. // current = next;
  273. // i++;
  274. // }
  275. // } else {
  276. // Node<E> prev = null;
  277. // int i = size - 1;
  278. // for (Node<E> current = tail; current != null;) {
  279. // if (i == index) {
  280. // targetNode = current;
  281. // break;
  282. // }
  283. // prev = current.prev;
  284. // current = prev;
  285. // i--;
  286. // }
  287. // }
  288. // return targetNode;
  289. // }
  290.  
  291. /*
  292. * 检查元素下标对应的元素是否存在
  293. *
  294. */
  295. private void checkElementIndex(int index) {
  296. if (index >= size || index < 0)
  297. throw new IndexOutOfBoundsException(indexOutRangeMsg(index));
  298.  
  299. }
  300.  
  301. /*
  302. * 检查在插入元素时,插入位置是否正确
  303. *
  304. */
  305. private void checkElementPosition(int index) {
  306. if (index > size || index < 0)
  307. throw new IndexOutOfBoundsException(indexOutRangeMsg(index));
  308.  
  309. }
  310.  
  311. private String indexOutRangeMsg(int index) {
  312. return " index [" + index + "] , size [" + size + "]";
  313. }
  314.  
  315. // 表示节点的静态内部类
  316. private static class Node<E> {
  317. // 前一个节点
  318. private Node<E> prev;
  319. // 下一个节点
  320. private Node<E> next;
  321. // 当前节点存储的数据
  322. private E data;
  323.  
  324. public Node(Node<E> prev, E data, Node<E> next) {
  325. super();
  326. this.prev = prev;
  327. this.next = next;
  328. this.data = data;
  329. }
  330.  
  331. }
  332.  
  333. @Override
  334. public Iterator<E> iterator() {
  335.  
  336. return new SimpleIterator();
  337. }
  338.  
  339. private class SimpleIterator implements Iterator<E> {
  340. private int desireModCount = modCount;// 预期的的修改次数
  341. private Node<E> nextNode = head;// 下次遍历的节点
  342. private Node<E> lastRetNode = null;// 最终使用的节点
  343. private int nextIndex = 0;// 下标
  344.  
  345. @Override
  346. public boolean hasNext() {
  347.  
  348. return nextIndex < size;
  349. }
  350.  
  351. @Override
  352. public E next() {
  353. checkModCount();
  354. if (!hasNext())
  355. throw new NoSuchElementException();
  356. lastRetNode = nextNode;
  357. E data = lastRetNode.data;
  358. nextNode = nextNode.next;
  359. nextIndex++;
  360. return data;
  361. }
  362.  
  363. @Override
  364. public void remove() {
  365. checkModCount();
  366. if (lastRetNode == null)
  367. throw new IllegalStateException("");
  368. Node<E> lastNode = lastRetNode.next;
  369. LinkList.this.unlink(lastRetNode);
  370.  
  371. if (lastNode == lastRetNode)
  372. nextNode = lastNode;
  373. else
  374. nextIndex--;
  375.  
  376. lastRetNode = null;
  377. desireModCount = modCount;
  378.  
  379. }
  380.  
  381. /*
  382. * 校验在迭代过程中,是否使用了非迭代的方式改变了链表结构
  383. */
  384. private void checkModCount() {
  385. if (desireModCount != modCount)
  386. throw new IllegalStateException("迭代过程中的结构被改变了");
  387. }
  388. }
  389.  
  390. @Override
  391. public boolean contains(Object o) {
  392.  
  393. return indexOf(o) != -1;
  394. }
  395.  
  396. @Override
  397. public Object[] toArray() {
  398. if (size == 0)
  399. return new Object[] {};
  400. Object[] objs = new Object[size];
  401. int i = 0;
  402. for (Node<E> next = head; next != null; next = next.next) {
  403. objs[i] = next.data;
  404. i++;
  405. }
  406. return objs;
  407. }
  408.  
  409. @SuppressWarnings("unchecked")
  410. @Override
  411. public <T> T[] toArray(T[] a) {
  412. if (a.length < size)
  413. a = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
  414. int i = 0;
  415. Object[] result = a;
  416. for (Node<E> x = head; x != null; x = x.next)
  417. result[i++] = x.data;
  418.  
  419. return a;
  420. }
  421.  
  422. @Override
  423. public boolean containsAll(Collection<?> c) {
  424. if (c == null)
  425. throw new NullPointerException();
  426.  
  427. for (Object o : c) {
  428. if (indexOf(o) == -1)
  429. return false;
  430. }
  431. return true;
  432. }
  433.  
  434. @Override
  435. public boolean addAll(Collection<? extends E> c) {
  436. if (c == null)
  437. throw new NullPointerException();
  438.  
  439. return addAll(size, c);
  440. }
  441.  
  442. @Override
  443. public boolean addAll(int index, Collection<? extends E> c) {
  444. checkElementPosition(index);
  445. if (c.size() == 0)
  446. return false;
  447. Node<E> prev, insertPoint;
  448. if (index == size) {
  449. prev = tail;
  450. insertPoint = null;
  451. } else {
  452. insertPoint = fastNodeAt(index);
  453. prev = insertPoint.prev;
  454. }
  455.  
  456. Object[] elementsAdd = c.toArray();
  457.  
  458. for (Object o : elementsAdd) {
  459. @SuppressWarnings("unchecked")
  460. E e = (E) o;
  461. Node<E> newNode = new Node<E>(prev, e, null);
  462. if (prev == null)
  463. head = newNode;
  464. else
  465. prev.next = newNode;
  466. prev = newNode;
  467. }
  468.  
  469. if (insertPoint == null)
  470. tail = prev;
  471. else {
  472. prev.next = insertPoint;
  473. insertPoint.prev = prev;
  474. }
  475.  
  476. size += elementsAdd.length;
  477. modCount++;
  478. return true;
  479. }
  480.  
  481. @Override
  482. public boolean removeAll(Collection<?> c) {
  483. if (c == null)
  484. throw new NullPointerException();
  485. boolean removeFlag = false;
  486. Iterator<E> itor = this.iterator();
  487. while (itor.hasNext()) {
  488. if (c.contains(itor.next())) {
  489. itor.remove();
  490. removeFlag = true;
  491.  
  492. }
  493.  
  494. }
  495. return removeFlag;
  496. }
  497.  
  498. @Override
  499. public boolean retainAll(Collection<?> c) {
  500.  
  501. if (c == null)
  502. throw new NullPointerException();
  503. boolean retainFlag = false;
  504. Iterator<E> itor = this.iterator();
  505. while (itor.hasNext()) {
  506. if (!c.contains(itor.next())) {
  507. itor.remove();
  508. retainFlag = true;
  509. }
  510. }
  511. return retainFlag;
  512. }
  513.  
  514. @Override
  515. public void clear() {
  516. if (size == 0)
  517. return;
  518. for (Node<E> curNode = head; curNode != null;) {
  519. Node<E> nextNode = curNode.next;
  520. curNode.prev = null;
  521. curNode.data = null;
  522. curNode.next = null;
  523. curNode = nextNode;
  524. }
  525. size = 0;
  526. head = null;
  527. tail = null;
  528. modCount++;
  529. }
  530.  
  531. @Override
  532. public ListIterator<E> listIterator() {
  533. throw new UnsupportedOperationException();
  534.  
  535. }
  536.  
  537. @Override
  538. public ListIterator<E> listIterator(int index) {
  539.  
  540. throw new UnsupportedOperationException();
  541. }
  542.  
  543. @Override
  544. public List<E> subList(int fromIndex, int toIndex) {
  545.  
  546. throw new UnsupportedOperationException();
  547. }
  548.  
  549. }

跟踪LinkedList源码,通过分析双向链表实现原理,自定义一个双向链表的更多相关文章

  1. Android版数据结构与算法(三):基于链表的实现LinkedList源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. LinkedList 是一个双向链表.它可以被当作堆栈.队列或双端队列进行操作.LinkedList相对于ArrayList来说,添加,删除元素效 ...

  2. Java面试题 从源码角度分析HashSet实现原理?

    面试官:请问HashSet有哪些特点? 应聘者:HashSet实现自set接口,set集合中元素无序且不能重复: 面试官:那么HashSet 如何保证元素不重复? 应聘者:因为HashSet底层是基于 ...

  3. 从源码角度分析 MyBatis 工作原理

    一.MyBatis 完整示例 这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的. 注:本文后面章节中的原理.源码部分也将基于这个示例来进行讲解.完整示例源码地址 1.1. 数据库准备 ...

  4. LinkedList源码解读

    一.内部类Node数据结构 在讲解LinkedList源码之前,首先我们需要了解一个内部类.内部类Node来表示集合中的节点,元素的值赋值给item属性,节点的next属性指向下一个节点,节点的pre ...

  5. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  6. 集合之LinkedList源码分析

    转载请注明出处:http://www.cnblogs.com/qm-article/p/8903893.html 一.介绍 在介绍该源码之前,先来了解一下链表,接触过数据结构的都知道,有种结构叫链表, ...

  7. ArrayList 和 LinkedList 源码分析

    List 表示的就是线性表,是具有相同特性的数据元素的有限序列.它主要有两种存储结构,顺序存储和链式存储,分别对应着 ArrayList 和 LinkedList 的实现,接下来以 jdk7 代码为例 ...

  8. LinkedList源码分析和实例应用

    1. LinkedList介绍 LinkedList是继承于AbstractSequentialList抽象类,它也可以被当作堆栈.队列或者双端队列使用. LinkedList实现了Deque接口,即 ...

  9. Java集合之LinkedList源码分析

    概述 LinkedLIst和ArrayLIst一样, 都实现了List接口, 但其内部的数据结构不同, LinkedList是基于链表实现的(从名字也能看出来), 随机访问效率要比ArrayList差 ...

随机推荐

  1. UVA - 548 Tree(二叉树的递归遍历)

    题意:已知中序后序序列,求一个叶子到根路径上权和最小,如果多解,则叶子权值尽量小. 分析:已知中序后序建树,再dfs求从根到各叶子的权和比较大小 #include<cstdio> #inc ...

  2. POJ 2142:The Balance

    The Balance Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 4781   Accepted: 2092 Descr ...

  3. 安装redis cluster时:undefined method `invoke_with_build_args' for nil:NilClass

    gem install -l redis-3.3.3.gem ERROR: Loading command: install (LoadError) cannot load such file -- ...

  4. 剑指offer - 顺时针打印矩阵 - JavaScript

    题目描述 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下 4 X 4 矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印 ...

  5. oracle中判断"非"

    在oracle中判断为"非"最常见的两种情况,一个是"不等于",一个的"非空". 通过查找资料得知,oracle中判断不等于的方法有好多种: ...

  6. Javascript观察者模式(Object.defineProperty、Reflect和Proxy实现)

    什么是观察者模式? 答:在数据发生改变时,对应的处理函数自动执行.函数自动观察数据对象,一旦对象有变化,函数就会自动执行. 参考<原生JavaScript实现观察者模式>(https:// ...

  7. 十三、react-router 4.x的基本配置

    路由的定义及作用 根组件根据客户端不同的请求网址显示时,要卸载上一个组件,再挂载下一个组件,如果手动操作话将是一个巨大麻烦.具体过程如下图: [根组件] ↑ [首页组件] [新闻组件] [商品组件] ...

  8. 线段树&树状数组与离散化的妙用

    牛客2019多校联盟Day7 Fine the median 题意:  每次给数组插入区间[Li,Ri] 内的所有数,每操作一次查询中位数. 遇到这题真的算是巧合,然而就是这种冥冥之中的缘分,给了我线 ...

  9. 【转载】WebDriver拾级而上·之零 WebDriver理论

    Selenium2.0 = Selenium1.0 + WebDriver(也就是说Selenium2.0合并了这两个项目)   Selenium1.0可以使用任何编程语言,但是有个先决条件就是必须支 ...

  10. springboot+thymeleaf项目中使用th:replace访问templates子目录下的模板,会报错找不到模板路径

    解决方法: 先将模板路径放置templates目录下,发现可以访问,说明th:replace是可以用的. 那可能是出现在路径问题上面. 于是我开始调错,改路径. 后来在网上查找资料.说了很多种方法. ...