Collection series—LinkedList source code analysis

Author: Lao Fuzi Link: http://www.cnblogs.com/liuyun1995/p/8287707.html


The LinkedList introduced in this article is another implementation of the List interface. Its bottom layer is implemented based on a doubly linked list, so it has the characteristics of fast insertion and deletion but slow search and modification. In addition, through the operation of the doubly linked list, queue and stack function.


The underlying structure of LinkedList is shown in the following figure.

F represents the reference of the head node, L represents the reference of the tail node, and each node of the linked list has three elements, which are the reference of the predecessor node (P), the value of the node element (E), and the reference of the successor node. (N). The node is represented by the inner class Node, let's take a look at its internal structure.

//结点内部类
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;
   }
}


The inner class Node is actually very simple. It has only three member variables and a constructor. item represents the value of the node, next is the reference of the next node, prev is the reference of the previous node, and these three are passed in through the constructor. value. Next, look at the member variables and constructors of LinkedList.

//集合元素个数
transient int size = 0;

//头结点引用
transient Node<E> first;

//尾节点引用
transient Node<E> last;

//无参构造器
public LinkedList() {}

//传入外部集合的构造器
public LinkedList(Collection<? extends E> c) {
   this();
   addAll(c);
}


LinkedList holds the reference of the head node and the reference of the tail node. It has two constructors, one is a no-argument constructor, and the other is a constructor that passes in an external collection. Unlike ArrayList, LinkedList does not have a constructor that specifies an initial size. Check out its CRUD method.

//增(添加)
public boolean add(E e) {
   //在链表尾部添加
   linkLast(e);
   return true;
}

//增(插入)
public void add(int index, E element) {
   checkPositionIndex(index);
   if (index == size) {
       //在链表尾部添加
       linkLast(element);
   } else {
       //在链表中部插入
       linkBefore(element, node(index));
   }
}

//删(给定下标)
public E remove(int index) {
   //检查下标是否合法
   checkElementIndex(index);
   return unlink(node(index));
}

//删(给定元素)
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;
}

//改
public E set(int index, E element) {
   //检查下标是否合法
   checkElementIndex(index);
   //获取指定下标的结点引用
   Node<E> x = node(index);
   //获取指定下标结点的值
   E oldVal = x.item;
   //将结点元素设置为新的值
   x.item = element;
   //返回之前的值
   return oldVal;
}

//查
public E get(int index) {
   //检查下标是否合法
   checkElementIndex(index);
   //返回指定下标的结点的值
   return node(index).item;
}


The method of adding elements of LinkedList mainly calls two methods, linkLast and linkBefore. The linkLast method is to link an element behind the linked list, and the linkBefore method is to insert an element in the middle of the linked list. The delete method of LinkedList removes an element from the linked list by calling the unlink method. Let's take a look at the core code of the insertion and deletion operations of the linked list.

//链接到指定结点之前
void linkBefore(E e, Node<E> succ) {
   //获取给定结点的上一个结点引用
   final Node<E> pred = succ.prev;
   //创建新结点, 新结点的上一个结点引用指向给定结点的上一个结点
   //新结点的下一个结点的引用指向给定的结点
   final Node<E> newNode = new Node<>(pred, e, succ);
   //将给定结点的上一个结点引用指向新结点
   succ.prev = newNode;
   //如果给定结点的上一个结点为空, 表明给定结点为头结点
   if (pred == null) {
       //将头结点引用指向新结点
       first = newNode;
   } else {
       //否则, 将给定结点的上一个结点的下一个结点引用指向新结点
       pred.next = newNode;
   }
   //集合元素个数加一
   size++;
   //修改次数加一
   modCount++;
}

//卸载指定结点
unlink(Node<E> x) {
   //获取给定结点的元素
   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;
}


linkBefore and unlink are representative operations of linking and unloading nodes. Other methods of linking and unloading nodes at both ends are similar, so we focus on linkBefore and unlink methods.

Process diagram of the linkBefore method:

Process diagram of the unlink method:

 

From the above diagram, it can be seen that the time complexity of the insertion and deletion of the linked list is O(1), and the search and modification operations of the linked list need to traverse the linked list to locate the elements, both of which are called node The (int index) method locates the element and sees how it locates the element by subscripting.

//根据指定位置获取结点
Node<E> node(int 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;
   }
}


When positioning by subscript, first determine whether it is in the upper or lower half of the linked list. If it is in the upper half, start from the beginning, and if it is in the lower half, start from the end. Therefore, through subscript search and modification The time complexity of the operation is O(n/2). Through the operation of the doubly linked list, the functions of single-item queue, bidirectional queue and stack can also be realized.

One-way queue operation:

//获取头结点
public E peek() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

//获取头结点
public E element() {
   return getFirst();
}

//弹出头结点
public E poll() {
   final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);
}

//移除头结点
public E remove() {
   return removeFirst();
}

//在队列尾部添加结点
public boolean offer(E e) {
   return add(e);
}


Two-way queue operation:

//在头部添加
public boolean offerFirst(E e) {
   addFirst(e);
   return true;
}

//在尾部添加
public boolean offerLast(E e) {
   addLast(e);
   return true;
}

//获取头结点
public E peekFirst() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

//获取尾结点
public E peekLast() {
   final Node<E> l = last;
   return (l == null) ? null : l.item;
}


stack operation:

//入栈
public void push(E e) {
   addFirst(e);
}

//出栈
public E pop() {
   return removeFirst();
}


Whether it is a one-way queue, a two-way queue or a stack, it actually operates on the head node and tail node of the linked list, and their implementations are based on addFirst(), addLast(), removeFirst(), removeLast(). These methods are similar in operation to linkBefore() and unlink(), except that one operates on both ends of the linked list, and the other operates on the middle of the linked list. It can be said that these four methods are special cases of linkBefore() and unlink() methods, so it is not difficult to understand their internal implementation, so I won't introduce them here. At this point, our analysis of LinkedList is about to end, and we will summarize the key points in the full text:

  • LinkedList is implemented based on a doubly linked list. Whether it is a method of adding, deleting, modifying or querying or implementing a queue and stack, it can be implemented by operating nodes.

  • LinkedList does not need to specify the capacity in advance, because based on the linked list operation, the capacity of the collection automatically increases as elements are added.

  • After the LinkedList deletes elements, the memory occupied by the collection is automatically reduced, and there is no need to call the trimToSize() method like an ArrayList.

  • All methods of LinkedList are not synchronized, so it is not thread-safe and should be avoided in multi-threaded environment

  • The above analysis is based on JDK1.7, and other versions will have some differences, so it cannot be generalized.


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324689106&siteId=291194637