LinkedList source code analysis and design ideas

LinkedList source code analysis

Like ArrayList, LinkedList also deals with collections, but the difference between the two can be seen from the name. The underlying implementation of the former is based on an array, and the latter is based on a linked list. LinkedList is more suitable for first-in first-out and first-in-last-out scenarios for collection elements, and is frequently used in the queue source code.

One: the overall structure

1.1, LinkedList structure

The underlying data structure of LinkedList is a doubly linked list. The overall structure is shown in the figure below:
LinkedList underlying implementation

The above figure represents a doubly linked list structure, each node in the linked list can be traced forward or backward, we can see from the above structure:

  1. first is the head node of the doubly linked list, and its previous node is null;
  2. last is the end node of the doubly linked list, and its next node is null;
  3. When there is no data in the linked list, first and last are the same node, and both point to null;

Each element in the linked list is called a Node, and the source code of Node is very simple, as shown below:

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

Two: source code analysis

2.1, add (add)

When adding a new node, we can choose to append to the head of the linked list or append to the end of the linked list. The add method defaults to append from the end, and the addFirst method appends from the head. Let’s look at two different append methods.

1), increase from the tail

// 从尾部开始追加节点
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++;
}

2), increase from the head

// 从头部追加
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++;
}

Since the bottom layer is based on a doubly linked list, all head append nodes and tail append nodes are very similar, except that the former is the prev point of the mobile head node, and the latter is the next point of the mobile tail node.

2.2, delete

The method of node deletion is similar to that of adding. We can choose to delete from the head or from the tail. The deletion operation will set the value of the node, the front and back pointing nodes to be null, to help the GC to recycle.
1), 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;
}

2) Delete from the tail

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

From the source code, we can understand that adding and deleting nodes of the linked list structure is very simple. Just modify the points of the front and back nodes, so the LinkedList is added and deleted very quickly.

2.3, node query

It is relatively slow to query a certain node in the linked list, and it needs to be searched one by one. Let's see how the source code of LinkedList finds nodes:

// 根据链表索引位置查询节点
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;
    }
}

From the source code, we can find that LinkedList does not use a loop from beginning to end, but a simple dichotomy. First, look at whether the index is in the first half or the second half of the linked list. If it is the first half, start from the beginning and vice versa. In this way, the number of cycles is reduced by at least half, and the search performance is improved.

2.4. Method comparison

LinkedList implements the Queue interface and adds a lot of new methods in terms of adding, deleting, querying, etc. These methods are especially easy to confuse in normal times. When the linked list is empty, the return value is also different. Let's take a look at the two Comparison of those:

Method meaning List Queue Low-level implementation
Add add() offer() The underlying implementation is the same
delete remove() poll() When the linked list is empty, remove throws an exception, poll returns null
Find element() peek() When the linked list is empty, element will throw an exception and peek will return null

LinkedList also implements the Deque (double-ended queue) interface. It provides methods for adding, deleting and searching from the beginning or the end, such as the remove method. Deque provides two directions of use: removeFirst and removeLast , But when the linked list is empty, the behavior is the same as the remove method, and an exception is thrown.

Three: Summary

LinkedList is suitable for scenarios that require order and iterates in order. It mainly depends on the underlying linked list structure. The frequency in interviews is still quite high.

Guess you like

Origin blog.csdn.net/weixin_38478780/article/details/107718588