LinkedList源码解析

Posted firepation

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinkedList源码解析相关的知识,希望对你有一定的参考价值。

1. 底层数据结构

在 LinkedList 中,定义了一个内部类 Node 来保存每个节点的信息。在这个内部类中,有一个 E 类型的变量,用于存储该节点的值;nextprev 变量存储着前后两个节点的地址,也是这两个变量是每个前后两个节点存在联系。

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

在 LinkedList 这个类中,定义了 first 和 last 两个变量来指向链表的头结点和尾节点。在源码中,作者还给了注释,由此可以看出,在存储过程中,第一个节点的 prev 是为 null 的,而第一个节点是有存储值的。最后一个节点也是有存储值的,但是最后一个节点的 next 属性是为 null 的。

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
 transient Node<E> first;

 /**
  * Pointer to last node.
  * Invariant: (first == null && last == null) ||
  *            (last.next == null && last.item != null)
  */
 transient Node<E> last;

2. 常用 API

2.1 添加元素

链表中的 add 方法是将指定元素添加到链表的末尾,主要是调用了 linkLast 方法。

public boolean add(E e) {
    linkLast(e);
    return true;
}

linkLast 方法是添加元素的关键,它首先构造一个节点,节点的前一个元素为 last 变量,后一个节点为 null。接着判断头结点是否为空,如果为空,表示该链表中还没有元素,则将头结点指向该节点。此时,头结点的 prev 变量指向的是 last,而 last 为 null,符合上面注释定义的规则。

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

2.2 删除元素

remove 函数主要是遍历整个链表,当找到要删除的节点时,使用 unlink 方法删除这个节点。

public boolean remove(Object o) {
     if (o == null) {
          for (Node<E> x = first; x != null; x = x.next) {
              if (x.item == null) {
                  unlink(x);
                  return true;
              }
          }
      } else {
          for (Node<E> x = first; x != null; x = x.next) {
              if (o.equals(x.item)) {
                  unlink(x);
                  return true;
              }
          }
      }
      return false;
  }

接下来我们就来看一下 unlink 函数的实现。

该函数首先判断 prev 变量是否为 null,如果为 null,则说明要删除的节点为头结点,删除头结点只需要 first 指向下一个节点即可。如果前一个节点不为 null,则将前一个节点的 next 指向 next 节点。

接着判断要 next 变量是否为 null,如果为 null,则说明该节点是最后一个节点,那么只要将 last 指向 prev 就可以删除和最后一个节点之间的联系了。如果不为 null,则将下一个节点的 prev 指向要删除节点的前一个节点,即 prev 变量。

E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

2.3 查找元素

查找元素是通过 get 这个方法来实现的,在方法内部,首先利用 checkElementIndex 方法检查 index 是否在指定范围内,如果不再指定范围内则抛出 IndexOutOfBoundsException 的异常。接着调用 node 方法查找指定元素。

在 node 方法内部,通过判断需要查找元素是在链表的前半段还是后半段,然后通过 prev 或者 next 遍历来查找,这是提升查找速度的一个小细节。

Node<E> node(int index) {
   // assert isElementIndex(index);

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

2.4 迭代器

集合框架实现迭代器的原理差不多,大致是这个模式,根据底层数据结构的不同实现 Itr 类中的 hasNext、remove、next 方法。

public Iterator<E> iterator(){
    return new Itr();
} 
private class Itr implements Iterator(){
    public boolean hasNext(){
    }
    public void remove(){
    }
    public E next(){
    }
}

以上是关于LinkedList源码解析的主要内容,如果未能解决你的问题,请参考以下文章

LinkedList源码解析

LinkedList源码解析

Java集合---LinkedList源码解析

Java集合---LinkedList源码解析

List源码解析之LinkedList 源码分析

JDK源码阅读:LinkedList源码解析