Source resolve LinkedList

LinkedList apply to the collection element FIFO and into and out of the scene after the first, is frequently used in the queue source code, are often asked to interview, this section allows us to deepen the understanding of the LinkedList by source.

1 overall architecture

LinkedList underlying data structure is a doubly linked list, the entire structure as shown below:

Here Insert Picture Description
The figure represents a doubly linked list structure, each node in the linked list can be traced forward or backward, we have several concepts as follows :

  1. Each node list we called Node, Node there prev attributes, position after position before representatives of a node, next property on behalf of a node;
  2. doubly linked list head node is the first of its previous node is null.
  3. last tail node is a doubly linked list, a node after it is null;
  4. When there is no data in the list, first and last nodes are the same, both before and after the point to null;
  5. Because it is a doubly linked list, as long as the machine's memory is strong enough, there is no size limit.

The list of elements, called Node, we look at part of the Node:

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

2 Source resolve

2.1 Additional (new)

When additional nodes, we can choose to be appended to the head of the list, or appended to the tail of the list, add additional default method is to start from the tail, addFirst additional method is to start from the head, we look separately at two different additional ways:

From the tail of the additional (add)

// 从尾部开始追加节点
void linkLast(E e) {
    // 把尾节点数据暂存
    final Node<E> l = last;
    // 新建新的节点,初始化入参含义:
    // l 是新节点的前一个节点,当前值是尾节点值
    // e 表示当前新增节点,当前新增节点后一个节点是 null
    final Node<E> newNode = new Node<>(l, e, null);
    // 新建节点追加到尾部
    last = newNode;
    //如果链表为空(l 是尾节点,尾节点为空,链表即空),头部和尾部是同一个节点,都是新建的节点
    if (l == null)
        first = newNode;    //否则把前尾节点的下一个节点,指向当前尾节点。
    else
        l.next = newNode;
    //大小和版本更改
    size++;
    modCount++;
}

From the source point of view, the tail node is added is relatively simple, can be simply put, we point to the location to be modified under the action figure to describe the whole process:
image description

From the head append (addFirst)

// 从头部追加
private void linkFirst(E e) {
    // 头节点赋值给临时变量
    final Node<E> f = first;
    // 新建节点,前一个节点指向null,e 是新建节点,f 是新建节点的下一个节点,目前值是头节点的值
    final Node<E> newNode = new Node<>(null, e, f);
    // 新建节点成为头节点
    first = newNode;
    // 头节点为空,就是链表为空,头尾节点是一个节点
    if (f == null)
        last = newNode;
    //上一个头节点的前一个节点指向当前节点
    else
        f.prev = newNode;
    size++;
    modCount++;
}

Adding additional head node and tail node is very similar, except that it is the head node to the mobile prev, which is the end of the mobile node to the next.

2.2 Delete Node

Additional nodes removed and a similar way, we can choose to be removed from the head, you can also choose to delete from the tail, the value of the operation will delete nodes, nodes around the point are set to null, helping GC recovered.

Delete from the head

//从头删除节点 f 是链表头节点
private E unlinkFirst(Node<E> f) {
    // 拿出头节点的值,作为方法的返回值
    final E element = f.item;
    // 拿出头节点的下一个节点
    final Node<E> next = f.next;
    //帮助 GC 回收头节点
    f.item = null;
    f.next = null;
    // 头节点的下一个节点成为头节点
    first = next;
    //如果 next 为空,表明链表为空
    if (next == null)
        last = null;
    //链表不为空,头节点的前一个节点指向 null
    else
        next.prev = null;
    //修改链表大小和版本
    size--;
    modCount++;
    return element;
}

To delete a node from the tail of the code is similar, it is not posted.

We can learn from the source code, the new node linked list structure, delete all very simple, just point to the next node before and after modification just fine, so LinkedList add and remove quickly.

2.3 node query

Query a list of a node is relatively slow, need one by one to find the job cycle, we see how the source code is looking for LinkedList node:

// 根据链表索引位置查询节点
Node<E> node(int index) {
    // 如果 index 处于队列的前半部分,从头开始找,size >> 1 是 size 除以 2 的意思。
    if (index < (size >> 1)) {
        Node<E> x = first;
        // 直到 for 循环到 index 的前一个 node 停止
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {// 如果 index 处于队列的后半部分,从尾开始找
        Node<E> x = last;
        // 直到 for 循环到 index 的后一个 node 停止
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

We can see from the source code, LinkedList does not use a loop from the beginning to the end of practice, but take a simple dichotomy, first look at index in the first half of the list, or the second half. If the first half, looking to start from scratch, and vice versa. In this way, the number of cycles is reduced by at least half, improve the performance of lookups, this idea is worth learning from.

2.4 Comparison Method

LinkedList implements the Queue interface, in add, delete, query and other aspects of the increase in the number of new methods that are particularly vulnerable to confusion in peacetime, when the list is empty, the return value is not the same, we form a column, to facilitate record:
Here Insert Picture Description

PS: Queue interfaces operate comment recommend add method throws an exception on failure, but add LinkedList implemented method always return true.
LinkedList also implements the Deque interface to add, delete, and find offers from scratch, or method from the beginning of the end of both orientations, like the remove method, Deque and provides usage removeFirst removeLast both orientations, but when the list is space-time performance, and remove methods are the same, it will throw an exception.

2.5 iterator

Because LinkedList iterations to achieve the two-way access, so we use the Iterator interface certainly die, because Iterator only supports access from start to finish. Java interface to add an iterative called: ListIterator, this interface provides forward and backward iterative method as follows:
Here Insert Picture Description
the LinkedList ListIterator implements the interface, as shown below:

// 双向迭代器
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;//上一次执行 next() 或者 previos() 方法时的节点位置
    private Node<E> next;//下一个节点
    private int nextIndex;//下一个节点的位置
    //expectedModCount:期望版本号;modCount:目前最新版本号
    private int expectedModCount = modCount;
    …………
}

We first look at the direction of iterations from start to finish:

// 判断还有没有下一个元素
public boolean hasNext() {
    return nextIndex < size;// 下一个节点的索引小于链表的大小,就有
}

// 取下一个元素
public E next() {
    //检查期望版本号有无发生变化
    checkForComodification();
    if (!hasNext())//再次检查
        throw new NoSuchElementException();
    // next 是当前节点,在上一次执行 next() 方法时被赋值的。
    // 第一次执行时,是在初始化迭代器的时候,next 被赋值的
    lastReturned = next;
    // next 是下一个节点了,为下次迭代做准备
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

Source above idea is taken directly to the next current node, the tail and head iteration slightly more complex, as follows:

// 如果上次节点索引位置大于 0,就还有节点可以迭代
public boolean hasPrevious() {
    return nextIndex > 0;
}
// 取前一个节点
public E previous() {
    checkForComodification();
    if (!hasPrevious())
        throw new NoSuchElementException();
    // next 为空场景:1:说明是第一次迭代,取尾节点(last);2:上一次操作把尾节点删除掉了
    // next 不为空场景:说明已经发生过迭代了,直接取前一个节点即可(next.prev)
    lastReturned = next = (next == null) ? last : next.prev;
    // 索引位置变化
    nextIndex--;
    return lastReturned.item;
}

Here reflected in the more complex needs to determine next empty and not empty scene, code comments are described in detail.

Iterator deleted

LinkedList when you delete an element, is also recommended for deletion by the iterator, the removal process is as follows:

public void remove() {
    checkForComodification();
    // lastReturned 是本次迭代需要删除的值,分以下空和非空两种情况:
    // lastReturned 为空,说明调用者没有主动执行过 next() 或者 previos(),直接报错
    // lastReturned 不为空,是在上次执行 next() 或者 previos()方法时赋的值
    if (lastReturned == null)
        throw new IllegalStateException();
    Node<E> lastNext = lastReturned.next;
    //删除当前节点
    unlink(lastReturned);
    // next == lastReturned 的场景分析:从尾到头递归顺序,并且是第一次迭代,并且要删除最后一个元素的情况下
    // 这种情况下,previous() 方法里面设置了 lastReturned = next = last,所以 next 和 lastReturned会相等
    if (next == lastReturned)
        // 这时候 lastReturned 是尾节点,lastNext 是 null,所以 next 也是 null,这样在 previous() 执行时,发现 next 是 null,就会把尾节点赋值给 next
        next = lastNext;
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

to sum up

LinkedList applications that require sequential, iterative and will be the scene of the order, is mainly dependent on the bottom of the list structure, the frequency of the interview is still quite high, I believe that the reason clearly the above source, the interview should be no problem to deal with.

Published 102 original articles · won praise 287 · views 410 000 +

Guess you like

Origin blog.csdn.net/qq_43229543/article/details/104226561