概述 LinkedList 与 ArrayList 一样实现 List 接口,只是 ArrayList 是 List 接口的大小可变数组的实现,LinkedList 是 List 接口基于链表的实现。基于链表实现的方式使得 LinkedList 在插入和删除时更优于 ArrayList,而随机访问则比 ArrayList 逊色些。


image.png

定义

  1. public class LinkedList<E>
  2. extends AbstractSequentialList<E>
  3. implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  4. {

继承自 AbstractSequentialList 类,AbstractSequentialList 类继承自 AbstractList 类,只支持按次序访问,而AbstractList 支持随机访问。

属性

  1. //链表长度
  2. transient int size = 0;
  3. //链表头结点
  4. transient Node<E> first;
  5. //链表尾结点
  6. transient Node<E> last;
  7. /**
  8. *结点类
  9. */
  10. private static class Node<E> {
  11. //结点里存储的数据
  12. E item;
  13. //前一个结点
  14. Node<E> next;
  15. //后一个结点
  16. Node<E> prev;
  17. Node(Node<E> prev, E element, Node<E> next) {
  18. this.item = element;
  19. this.next = next;
  20. this.prev = prev;
  21. }
  22. }

构造器

  1. public LinkedList() {
  2. }
  3. //把集合内元素按顺序转换为链表
  4. public LinkedList(Collection<? extends E> c) {
  5. this();
  6. addAll(c);
  7. }

第二个构造器接收一个集合,调用默认构造器构建空链表,之后调用 addAll() 方法,将集合内元素按迭代器返回的顺序添加到链表里。

  1. /**
  2. *将集合内元素按顺序全部添加到链表里
  3. *@param c 待添加元素集合
  4. */
  5. public boolean addAll(Collection<? extends E> c) {
  6. return addAll(size, c);
  7. }
  8. /**
  9. *@param index 插入的结点位置
  10. *@param c 待插入元素的集合
  11. */
  12. public boolean addAll(int index, Collection<? extends E> c) {
  13. //检测index是否在范围内 0 <= index <= size
  14. checkPositionIndex(index);
  15. //将集合转为数组
  16. Object[] a = c.toArray();
  17. //获取(数组大小)元素个数
  18. int numNew = a.length;
  19. //空集合,返回false
  20. if (numNew == 0)
  21. return false;
  22. //定义两个结点,前驱结点和后继结点
  23. Node<E> pred, succ;
  24. //如果插入位置为当前链表大小,尾插,前驱结点为当前链表尾结点
  25. if (index == size) {
  26. succ = null;
  27. pred = last;
  28. } else {
  29. //查找结点,作为后继结点
  30. succ = node(index);
  31. //保存该结点的前驱结点
  32. pred = succ.prev;
  33. }
  34. //forEach遍历数组
  35. for (Object o : a) {
  36. @SuppressWarnings("unchecked") E e = (E) o;
  37. //构建新结点,元素为e,前驱结点为pred,后继结点为null
  38. Node<E> newNode = new Node<>(pred, e, null);
  39. //如果前驱结点为null,该结点作为该链表的头结点
  40. if (pred == null)
  41. first = newNode;
  42. else
  43. //否则该结点连接前驱结点的后继结点(往后链接结点)
  44. pred.next = newNode;
  45. //新的结点作为前驱结点(指针向后移)
  46. pred = newNode;
  47. }
  48. //遍历完后,如果后继结点为null,即28行的情况,尾插的情况,保存链表新的尾结点
  49. if (succ == null) {
  50. last = pred;
  51. } else {
  52. //不是尾插,即把后面部分链接起来
  53. pred.next = succ;
  54. succ.prev = pred;
  55. }
  56. //保存新的链表长度
  57. size += numNew;
  58. modCount++;
  59. return true;
  60. }

addAll方法大概的思路是判断链表添加元素是否是尾插,如果是,直接向链表后链接新结点,原尾结点作为第一次操作的位置;如果是在链表中间执行操作,则用pred,succ保存指定位置分开后待链接的前半部分和后半部分的链,在从pred开始操作完链接操作后,把原后半部分的链链接回去。

插入结点

对于链表来说,向链表中插入元素即是新建一结点,然后对链表进行新结点的插入链接操作。

  1. /**
  2. *插入元素方法,默认尾插
  3. */
  4. public boolean add(E e) {
  5. linkLast(e);
  6. return true;
  7. }
  8. public void addFirst(E e) {
  9. linkFirst(e);
  10. }
  11. public void addLast(E e) {
  12. linkLast(e);
  13. }
  14. /**
  15. *向指定位置插入元素
  16. *@param index 第几个结点
  17. */
  18. public void add(int index, E element) {
  19. //下标范围检查
  20. checkPositionIndex(index);
  21. //如果下标为链表大小,尾插,否则头插
  22. if (index == size)
  23. linkLast(element);
  24. else
  25. linkBefore(element, node(index));
  26. }
  27. /**
  28. *头插,向链表前面插入结点
  29. */
  30. private void linkFirst(E e) {
  31. //记录头结点
  32. final Node<E> f = first;
  33. //构造新结点,后继结点为当前链表头结点
  34. final Node<E> newNode = new Node<>(null, e, f);
  35. //保存新的头结点
  36. first = newNode;
  37. //如果当前头结点为null,即空链表,则此时只有一个结点,尾结点也为新结点
  38. //否则保存前驱结点
  39. if (f == null)
  40. last = newNode;
  41. else
  42. f.prev = newNode;
  43. //链表大小++
  44. size++;
  45. modCount++;
  46. }
  47. /**
  48. *尾插,向链表尾部插入结点
  49. */
  50. void linkLast(E e) {
  51. //记录尾结点
  52. final Node<E> l = last;
  53. //构建新结点,前驱结点为原链表尾结点
  54. final Node<E> newNode = new Node<>(l, e, null);
  55. //保存新的尾结点
  56. last = newNode;
  57. //如果尾结点为null,空链表,此时只有一个结点,头结点也为新结点
  58. //否则保存后继结点
  59. if (l == null)
  60. first = newNode;
  61. else
  62. l.next = newNode;
  63. //链表大小++
  64. size++;
  65. modCount++;
  66. }
  67. /**
  68. *向链表某个结点前插入结点
  69. */
  70. void linkBefore(E e, Node<E> succ) {
  71. // assert succ != null;
  72. //由index查找到的结点,记录此结点的前驱
  73. final Node<E> pred = succ.prev;
  74. //构建新结点,前驱为指定结点的前驱,后继为指定结点
  75. final Node<E> newNode = new Node<>(pred, e, succ);
  76. //保存新的前驱结点
  77. succ.prev = newNode;
  78. //指定结点前驱为null,头结点
  79. if (pred == null)
  80. //记录新的头结点
  81. first = newNode;
  82. else
  83. pred.next = newNode;
  84. size++;
  85. modCount++;
  86. }

删除结点

  1. /**
  2. *移除头结点
  3. */
  4. public E removeFirst() {
  5. //保存头结点
  6. final Node<E> f = first;
  7. //如果链表为空,抛异常
  8. if (f == null)
  9. throw new NoSuchElementException();
  10. //否则调用unlinkFirst()方法
  11. return unlinkFirst(f);
  12. }
  13. /**
  14. *移除尾结点,原理同上
  15. */
  16. public E removeLast() {
  17. final Node<E> l = last;
  18. if (l == null)
  19. throw new NoSuchElementException();
  20. return unlinkLast(l);
  21. }
  22. /**
  23. *移除链表第一次出现的指定元素的方法
  24. *@param o 指定元素
  25. */
  26. public boolean remove(Object o) {
  27. //如果o为null,移除为数据域null的结点
  28. if (o == null) {
  29. //遍历,移除
  30. for (Node<E> x = first; x != null; x = x.next) {
  31. if (x.item == null) {
  32. unlink(x);
  33. return true;
  34. }
  35. }
  36. } else {
  37. //不为null,移除指定的,遍历,移除
  38. for (Node<E> x = first; x != null; x = x.next) {
  39. if (o.equals(x.item)) {
  40. unlink(x);
  41. return true;
  42. }
  43. }
  44. }
  45. return false;
  46. }
  47. /**
  48. *移除指定下标的结点
  49. */
  50. public E remove(int index) {
  51. //检查下标范围
  52. checkElementIndex(index);
  53. //调用unlink(node(index))方法
  54. return unlink(node(index));
  55. }
  56. /**
  57. *默认移除头结点方法
  58. */
  59. public E remove() {
  60. return removeFirst();
  61. }
  62. /**
  63. *移除链表中第一次出现的指定元素结点
  64. *@param o 指定元素
  65. */
  66. public boolean removeFirstOccurrence(Object o) {
  67. return remove(o);
  68. }
  69. /**
  70. *移除链表最后一次出现指定元素结点
  71. *@param o 指定元素
  72. */
  73. public boolean removeLastOccurrence(Object o) {
  74. //该方法同remove(Object o),只是前者是从前往后遍历,该方法从后往前遍历,相当于从前往后的最后一次
  75. if (o == null) {
  76. for (Node<E> x = last; x != null; x = x.prev) {
  77. if (x.item == null) {
  78. unlink(x);
  79. return true;
  80. }
  81. }
  82. } else {
  83. for (Node<E> x = last; x != null; x = x.prev) {
  84. if (o.equals(x.item)) {
  85. unlink(x);
  86. return true;
  87. }
  88. }
  89. }
  90. return false;
  91. }
  92. /**
  93. *断链头结点方法
  94. *@param f 头结点
  95. */
  96. private E unlinkFirst(Node<E> f) {
  97. // assert f == first && f != null;
  98. //记录头结点的元素值
  99. final E element = f.item;
  100. //记录第二个结点
  101. final Node<E> next = f.next;
  102. //头结点数据域和后继置null,待GC
  103. f.item = null;
  104. f.next = null;
  105. //第二个结点为头指针
  106. first = next;
  107. //如果第二个指针为null,表明原链表只有一个结点,尾结点也是null
  108. if (next == null)
  109. last = null;
  110. else
  111. //否则头结点前驱置null
  112. next.prev = null;
  113. size--;
  114. modCount++;
  115. return element;
  116. }
  117. /**
  118. *断链尾结点方法
  119. *@param l 尾结点
  120. */
  121. private E unlinkLast(Node<E> l) {
  122. //记录尾结点的元素值
  123. final E element = l.item;
  124. //记录倒数第二个结点
  125. final Node<E> prev = l.prev;
  126. //置null,待GC
  127. l.item = null;
  128. l.prev = null;
  129. //倒数第二个结点为新的尾结点
  130. last = prev;
  131. //若倒数第二个结点为null,即原链表只有一个结点,头结点也置null
  132. if (prev == null)
  133. first = null;
  134. else
  135. //否则后继置null
  136. prev.next = null;
  137. size--;
  138. modCount++;
  139. return element;
  140. }
  141. /**
  142. *断链指定结点
  143. *@param x 指定结点
  144. */
  145. E unlink(Node<E> x) {
  146. // assert x != null;
  147. //记录该结点元素值
  148. final E element = x.item;
  149. //记录该结点前驱和后继结点
  150. final Node<E> next = x.next;
  151. final Node<E> prev = x.prev;
  152. //如果前驱结点为null,即该结点为头结点,新头结点为原结点后继
  153. if (prev == null) {
  154. first = next;
  155. } else {
  156. //前驱的next指向下一个
  157. prev.next = next;
  158. x.prev = null;
  159. }
  160. //如果后继结点为null,该结点为尾结点,新尾结点为原结点前驱
  161. if (next == null) {
  162. last = prev;
  163. } else {
  164. //后继的prev指向前一个
  165. next.prev = prev;
  166. x.next = null;
  167. }
  168. //置null待GC
  169. x.item = null;
  170. size--;
  171. modCount++;
  172. return element;
  173. }
  174. /**
  175. *移除所有元素
  176. */
  177. public void clear() {
  178. // Clearing all of the links between nodes is "unnecessary", but:
  179. // - helps a generational GC if the discarded nodes inhabit
  180. // more than one generation
  181. // - is sure to free memory even if there is a reachable Iterator
  182. for (Node<E> x = first; x != null; ) {
  183. //遍历链表,循环置null,待GC
  184. Node<E> next = x.next;
  185. x.item = null;
  186. x.next = null;
  187. x.prev = null;
  188. x = next;
  189. }
  190. first = last = null;
  191. size = 0;
  192. modCount++;
  193. }

查找方法

/**
 *获取头结点元素
 */
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

/**
 *获取尾结点元素
 */
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

/**
 *获取指定下标结点元素
 */
public E get(int index) {
    //下标范围检查
    checkElementIndex(index);
    return node(index).item;
}

/**
 *返回指定元素首次出现的下标位置,若无返回-1
 *@param o 指定元素
 */
public int indexOf(Object o) {
    int index = 0;
    //遍历链表,index++,找到元素后返回index值
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

/**
 *返回指定元素最后一次出现的下标位置,若无返回-1
 *该方法同上,前者从前往后遍历链表,该方法从后往前遍历链表,等同于最后一次
 */
public int lastIndexOf(Object o) {
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

/**
 *根据索引寻找结点,
 */
Node<E> node(int index) {
    // assert isElementIndex(index);
    //size >> 1 index小于size/2,则前半段遍历,这一操作相当于加速操作,优化了查找速度
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //否则后半段遍历
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

优点

  • LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据
  • 对于新增数据,LinkedList可以动态的分配内存
  • 线程安全

缺点

  • 对于随机访问方法,LinkedList的效率相较ArrayList会比较低,因为它要从头遍历链表
  • LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为

总结: 大多数情况下使用ArrayList;在数据量大且需要频繁读取集合中的元素时,使用ArrayList效率较高,而在插入和删除操作较多时,使用LinkedList效率较高。

  • 在数据量少的时候,两者效率相差不大。
  • 对于中间插入删除操作,LinkedList效率比ArrayList效率高,因为ArrayList还要进行数据的移动。
  • 对于查找操作,ArrayList效率远比LinkedList高,因为LinkedList还要遍历链表。