[关闭]
@kiraSally 2018-03-12T10:39:46.000000Z 字数 9932 阅读 2726

集合番@LinkedList一文通(1.7版)

JAVA COLLECTIONS 源码 1.7版本


1.LinkedList的定义

2.LinkedList的数据结构

2.1 类定义

  1. public class LinkedList<E>
  2. extends AbstractSequentialList<E>
  3. implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • 继承 AbstractSequentialList,能被当作堆栈、队列或双端队列进行操作
  • 实现 List 接口,能进行队列操作
  • 实现 Deque 接口,能将LinkedList当作双端队列使用
  • 实现 Cloneable 接口,重写 clone() ,能克隆(浅拷贝)
  • 实现 java.io.Serializable 接口,支持序列化

2.2 重要全局变量

  1. /**
  2. * 当前链表元素数量
  3. */
  4. transient int size = 0;
  5. /**
  6. * Pointer to first node.
  7. * Invariant: (first == null && last == null) || (first.prev == null && first.item != null)
  8. * 链表头部节点
  9. */
  10. transient Node<E> first;
  11. /**
  12. * Pointer to last node.
  13. * Invariant: (first == null && last == null) || (last.next == null && last.item != null)
  14. * 链表尾部节点
  15. */
  16. transient Node<E> last;

2.3 构造器

  1. /**
  2. * Constructs an empty list.
  3. * 默认空构造器 -- 注意LinkedList并不提供指定容量的构造器
  4. */
  5. public LinkedList() {
  6. }
  7. /**
  8. * Constructs a list containing the elements of the specified
  9. * collection, in the order they are returned by the collection's iterator.
  10. * 支持将一个Collection转换成LinkedList
  11. *
  12. * @param c the collection whose elements are to be placed into this list
  13. * @throws NullPointerException if the specified collection is null
  14. */
  15. public LinkedList(Collection<? extends E> c) {
  16. this();
  17. addAll(c);
  18. }

2.4 Node

  1. /**
  2. * 存储对象的结构:
  3. * 每个Node节点包含了上一个节点和下一个节点的引用,从而构成了双向的链表
  4. */
  5. private static class Node<E> {
  6. E item;//存储元素
  7. Node<E> next;// 指向下一个节点
  8. Node<E> prev;// 指向上一个节点
  9. //注意第一个元素是prev,第二个元素才是存储元素即可
  10. Node(Node<E> prev, E element, Node<E> next) {
  11. this.item = element;
  12. this.next = next;
  13. this.prev = prev;
  14. }
  15. }

3.LinkedList的存储

3.1 add方法

  1. /**
  2. * Appends the specified element to the end of this list.
  3. * <p>This method is equivalent to {@link #addLast}.
  4. * 插入一个新元素到链表尾部
  5. * @param e element to be appended to this list
  6. * @return {@code true} (as specified by {@link Collection#add}) 返回插入结果
  7. */
  8. public boolean add(E e) {
  9. linkLast(e);
  10. return true;
  11. }
  12. /**
  13. * Inserts the specified element at the specified position in this list.
  14. * Shifts the element currently at that position (if any) and any
  15. * subsequent elements to the right (adds one to their indices).
  16. * 插入一个新元素到指定下标位置,大于该下标的所有元素统一向右移动一位
  17. * @param index index at which the specified element is to be inserted
  18. * @param element element to be inserted
  19. * @throws IndexOutOfBoundsException {@inheritDoc}
  20. */
  21. public void add(int index, E element) {
  22. checkPositionIndex(index);//下标边界校验
  23. if (index == size) //当下标==链表长度时,尾部插入
  24. linkLast(element);
  25. else
  26. linkBefore(element, node(index));//否则,前部插入(起始位置为index)
  27. }

3.2 checkPositionIndex方法

  1. private void checkPositionIndex(int index) {
  2. if (!isPositionIndex(index))
  3. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  4. }
  5. /**
  6. * Tells if the argument is the index of a valid position for an iterator or an add operation.
  7. * 当迭代或插入操作时,需要判断下标的边界
  8. */
  9. private boolean isPositionIndex(int index) {
  10. return index >= 0 && index <= size;
  11. }

3.3 linkLast方法

  1. /**
  2. * Links e as last element.
  3. * 将e变为链表的最后一个元素
  4. */
  5. void linkLast(E e) {
  6. final Node<E> l = last;
  7. final Node<E> newNode = new Node<>(l, e, null);//注意:新建node的next为null
  8. last = newNode;//将新建node作为链表尾部节点
  9. //当原队尾为null时,即链表为空时
  10. if (l == null)
  11. first = newNode;//将新建node同时作为链表头部节点
  12. else
  13. l.next = newNode;//将原链表尾部节点的next引用指向新建node,形成链表结构
  14. size++;//当前链表长度+1
  15. modCount++;//新增操作属于结构性变动,modCount计数+1
  16. }

3.4 linkBefore方法

  1. /**
  2. * Inserts element e before non-null Node succ.
  3. * 插入一个新的元素到指定非空节点之前
  4. */
  5. void linkBefore(E e, Node<E> succ) {
  6. // assert succ != null;
  7. //逻辑与linkLast基本一致,区别在于将last变成prev,将新节点插入到succ节点之前
  8. final Node<E> pred = succ.prev;
  9. final Node<E> newNode = new Node<>(pred, e, succ);
  10. succ.prev = newNode;//唯一的区别,将新节点插入到succ节点之前
  11. if (pred == null)
  12. first = newNode;
  13. else
  14. pred.next = newNode;
  15. size++;
  16. modCount++;//插入属于结构性变动,modCount计数+1
  17. }

3.5 linkBefore与空指针

4.LinkedList的读取

4.1 get方法

  1. /**
  2. * Returns the element at the specified position in this list.
  3. * 获取指定下标元素
  4. * @param index index of the element to return
  5. * @return the element at the specified position in this list
  6. * @throws IndexOutOfBoundsException {@inheritDoc}
  7. */
  8. public E get(int index) {
  9. checkElementIndex(index);
  10. return node(index).item; //注意返回不是node,而是item;同时node一定不为null,而item允许为null
  11. }

4.2 checkElementIndex方法

  1. private void checkElementIndex(int index) {
  2. if (!isElementIndex(index))
  3. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  4. }
  5. /**
  6. * Tells if the argument is the index of an existing element.
  7. * 判断当前下标是否存在元素,原理参见`linkBefore`
  8. * 有心的读者可能发现了:
  9. * (get)isElementIndex = index >= 0 && index < size;
  10. * (add)isPositionIndex = index >= 0 && index <= size;
  11. * 两个方法几乎一致,但有一个很明显的区别: isPositionIndex 多了个index=size的判断!!
  12. * 由此(从方法名也可知-论方法名的严谨性)可知 :
  13. * get时下标必须小于size,否则下标越界;
  14. * add(指定元素)时 :
  15. * 当index = size时,采用尾部插入,因为要占据下标为size的位置;
  16. * 当index < size时,采用前部插入,因为要占据下标为index的位置,同时原位置元素后移;
  17. */
  18. private boolean isElementIndex(int index) {
  19. return index >= 0 && index < size;
  20. }

4.3 node方法

  1. /**
  2. * Returns the (non-null) Node at the specified element index.
  3. * 返回指定下标的非空node
  4. * 这里笔者有个疑问:node本身没有动词形式,为何不是getNode?
  5. * 顺便提一句:nodeJs真的不错!
  6. */
  7. Node<E> node(int index) {
  8. // assert isElementIndex(index);
  9. //值得一提的是,为了提高查询效率,node查询选择使用二分查找法
  10. //个人观点:但可能因为Josh Bloch大神认为LinkedList并不适合查找,因此只是简单进行了一次二分而已
  11. if (index < (size >> 1)) {
  12. Node<E> x = first; //若在前半边,就从前往后找
  13. for (int i = 0; i < index; i++)
  14. x = x.next;
  15. return x;
  16. } else {
  17. Node<E> x = last; //若在后半边,就从后往前找
  18. for (int i = size - 1; i > index; i--)
  19. x = x.prev;
  20. return x;
  21. }
  22. }

5.LinkedList的移除

5.1 remove方法

  1. /**
  2. * Retrieves and removes the head (first element) of this list.
  3. * 默认删除头部节点
  4. * @return the head of this list
  5. * @throws NoSuchElementException if this list is empty
  6. * @since 1.5
  7. */
  8. public E remove() {
  9. return removeFirst();
  10. }
  11. /**
  12. * Removes the element at the specified position in this list. Shifts any
  13. * subsequent elements to the left (subtracts one from their indices).
  14. * Returns the element that was removed from the list.
  15. * 根据下标删除元素
  16. * @param index the index of the element to be removed
  17. * @return the element previously at the specified position
  18. * @throws IndexOutOfBoundsException {@inheritDoc}
  19. */
  20. public E remove(int index) {
  21. checkElementIndex(index);//边界校验 index >= 0 && index < size
  22. return unlink(node(index));//解绑操作
  23. }
  24. /**
  25. * Removes the first occurrence of the specified element from this list,if it is present.
  26. * If this list does not contain the element, it is unchanged.
  27. * More formally, removes the element with the lowest index {@code i} such that
  28. * <tt>(o==null?get(i)==null:;o.equals(get(i)))</tt> (if such an element exists).
  29. * Returns {@code true} if this list contained the specified element (or equivalently,
  30. * if this list changed as a result of the call).
  31. * 直接移除某个元素:
  32. * 当该元素不存在,不会发生任何变化
  33. * 当该元素存在且成功移除时,返回true,否则false
  34. * 当有重复元素时,只删除第一次出现的同名元素 :
  35. * 例如只移除第一次出现的null(即下标最小时出现的null)
  36. * @param o element to be removed from this list, if present
  37. * @return {@code true} if this list contained the specified element
  38. */
  39. public boolean remove(Object o) {
  40. //虽然跟ArrayList一样需要遍历,但由于不需要调用耗时的`System.arraycopy`,效率更高
  41. if (o == null) {
  42. for (Node<E> x = first; x != null; x = x.next) {
  43. if (x.item == null) {
  44. unlink(x);
  45. return true;
  46. }
  47. }
  48. } else {
  49. for (Node<E> x = first; x != null; x = x.next) {
  50. if (o.equals(x.item)) {
  51. unlink(x);
  52. return true;
  53. }
  54. }
  55. }
  56. return false;
  57. }

5.2 unlink方法

  1. /**
  2. * Unlinks non-null node x.
  3. * 解除node链接,主要干了三件事情:
  4. * 1.解绑当前元素的前后节点链接,前后节点重新绑定关系
  5. * 2.当前元素的所有属性清空,help gc
  6. * 3.链表长度-1,modCount计数+1(help fail-fast)
  7. * @return 返回元素本身 注意是item,而不是node
  8. */
  9. E unlink(Node<E> x) {
  10. // assert x != null;
  11. final E element = x.item;
  12. final Node<E> next = x.next;//后一位节点
  13. final Node<E> prev = x.prev;//前一位节点
  14. //解绑前一位节点
  15. if (prev == null) {//当前节点位于链表头部
  16. first = next;//后一位节点放链表头部
  17. } else {
  18. //非链表头部
  19. prev.next = next;//将前一位节点的next指向下一位节点
  20. x.prev = null;//当前节点的前一位节点清空 ,help gc
  21. }
  22. //解绑后一位节点
  23. if (next == null) {//当前节点位于链表尾部
  24. last = prev;//前一位节点放链表尾部
  25. } else {
  26. //非链表尾部
  27. next.prev = prev;//将后一位节点的prev指向前一位节点
  28. x.next = null;//当前节点的后一位节点清空 ,help gc
  29. }
  30. x.item = null;//当前节点元素清空
  31. size--;//链表长度-1
  32. modCount++;//删除操作属于结构性变动,modCount计数+1
  33. return element;//返回元素本身
  34. }

5.3 unlinkFirst方法

  1. /**
  2. * Unlinks non-null first node f.
  3. * 解绑头部
  4. */
  5. private E unlinkFirst(Node<E> f) {
  6. // assert f == first && f != null;
  7. final E element = f.item;
  8. final Node<E> next = f.next;
  9. f.item = null;
  10. f.next = null; // help GC
  11. first = next; //下一个节点作为新的头部节点
  12. if (next == null) //头部已经为null,那么尾部也是null
  13. last = null;
  14. else
  15. next.prev = null;//新头部节点的prev需要清空,非闭环
  16. size--;
  17. modCount++;
  18. return element;
  19. }

5.4 unlinkLast方法

  1. /**
  2. * Unlinks non-null last node l.
  3. * 解绑尾部
  4. */
  5. private E unlinkLast(Node<E> l) {
  6. // assert l == last && l != null;
  7. final E element = l.item;
  8. final Node<E> prev = l.prev;
  9. l.item = null;
  10. l.prev = null; // help GC
  11. last = prev; //上一个节点作为新的尾部节点
  12. if (prev == null) //尾部已经为null,那么头部也是null
  13. first = null;
  14. else
  15. prev.next = null;//新尾部节点的next需要清空,非闭环
  16. size--;
  17. modCount++;
  18. return element;
  19. }

6.LinkedList的其他主要API整理

6.1 查找

  • 获取头部节点:getFirst()peekFirst()peek()element()
  • 获取尾部节点:getLast()peekLast()
    NoSuchElementExceptiongetFirst()getLast()
    nullpeekFirst()peekLast()
    peek() 等同于 peekFirst()element() 内部调用的 getFirst()

6.2 插入

  • 头部插入:addFirst(E e)offerFirst(E e)
  • 尾部插入:addLast(E e)offerLast(E e)
    voidaddFirst(E e)addLast(E e)push(E e)
    booleanadd(E e)offer(E e)offerFirst(E e)offerLast(E e)
    linkLast(E e) 等同于 add(E e),大部分方法底层实现为 linkFirst(E e)linkLast(E e)

6.3 删除

  • 头部删除:removeFirst()poll()pollFirst()pop()
  • 尾部删除:removeLast()pollLast()
    NoSuchElementExceptionremove()removeFirst()removeLast()removeLast()
    nullpoll()pollFirst()pollLast()
    remove() 等同于 removeFirst(),大部分方法底层实现为 unlinkFirst(Node<E> n)unlinkLast(Node<E> n)
    removeLastOccurrence(Object o)remove(Object o)的实现几乎相同,区别前者是倒序查找,后者是正序查找

7.LinkedList的栈实现

  • 栈(Stack)是限定仅在一端进行插入和删除运算的线性表
  • 根据后进先出(LIFO)原则,顶部称为栈顶(top),底部称为栈底(bottom)
  1. /**
  2. * 队列的LinkedList版本简单实现
  3. * 这里使用first(使用last原理也一样,保证只在一端操作即可)
  4. */
  5. class Stack<T> {
  6. LinkedList<T> linkedList = new LinkedList<T>();
  7. /**
  8. * 入栈
  9. */
  10. public void push(T v) {
  11. linkedList.addFirst(v);
  12. }
  13. /**
  14. * 出栈,不删除栈顶元素
  15. */
  16. public T peek() {
  17. return storage.getFirst();
  18. }
  19. /**
  20. * 出栈 ,删除栈顶元素
  21. */
  22. public T pop() {
  23. return storage.removeFirst();
  24. }
  25. }

8.LinkedList的队列实现

  • 队列(Queue)是限定插入和删除各在一端进行的线性表
  • 根据先进先出(FIFO)原则,表中允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)
  1. /**
  2. * 队列的LinkedList版本简单实现
  3. * 这里使用队尾插入,对头删除的写法(反过来原理一致,只要保证插入和删除各占一端即可)
  4. */
  5. class Queue<T> {
  6. LinkedList<T> linkedList = new LinkedList<T>();
  7. /**
  8. * 入队,将指定的元素插入队尾
  9. */
  10. public void offer(T v) {
  11. linkedList.offer(v);
  12. }
  13. /**
  14. * 出队,获取头部元素,但不删除,如果此队列为空,则返回 null
  15. */
  16. public T peek() {
  17. return linkedList.peek();
  18. }
  19. /**
  20. * 出队,获取头部元素,但不删除,如果此队列为空,则抛异常
  21. */
  22. public T element() {
  23. return linkedList.element();
  24. }
  25. /**
  26. * 出队,获取头部元素并删除,如果队列为空,则返回 null
  27. */
  28. public T poll() {
  29. return linkedList.poll();
  30. }
  31. /**
  32. * 出队,获取头部元素并删除,如果队列为空,则抛异常
  33. */
  34. public T remove() {
  35. return linkedList.remove();
  36. }
  37. }

集合番@LinkedList一文通(1.7版)黄志鹏kira 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议 进行许可。

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注