Analysis of the source code of the Java collection LinkedList

Analysis of the source code of the Java collection LinkedList

Baiyu IT Haha
LinkedList also implements the List interface like ArrayList, but it is more efficient than ArrayList when inserting and deleting operations, because it is based on a linked list. Based on the linked list, it also determines that it is a bit inferior to ArrayList in terms of random access.

In addition, LinkedList also provides some methods that can be used as stacks, queues, and deques. Some of these methods are just names that differ from each other to make these names more appropriate in a specific context.

First look at the definition of the LinkedList class.


public class LinkedList<E>
   extends AbstractSequentialList<E>
   implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList inherits from AbstractSequenceList and implements the List and Deque interfaces. In fact, AbstractSequenceList has implemented the List interface, and it is only clearer to mark the List here. AbstractSequenceList provides a backbone implementation of the List interface to reduce the complexity of implementing the List interface. The Deque interface defines the operation of the deque.

Two attributes are defined in LinkedList:


private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;

The size must be the number of elements stored in the LinkedList object. Since LinkedList is implemented based on a linked list, this header must be the head node of the linked list, and Entry is the node object. The following is the code of the Entry class.


private static class Entry<E> {
     E element;
     Entry<E> next;
     Entry<E> previous; 
     Entry(E element, Entry<E> next, Entry<E> previous) {
         this.element = element;
         this.next = next;
         this.previous = previous;
     }
 }

Only the stored element, the previous element, and the next element are defined. This is the definition of the nodes of the doubly linked list. Each node only knows its previous node and the next node.
Look at the construction method of LinkedList.


public LinkedList() {
     header.next = header.previous = header;
}
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

LinkedList provides two construction methods. The first construction method does not accept parameters, but sets both the previous node and the next node of the header node to itself (note that this is a two-way circular linked list, if it is not a circular linked list, the case of an empty linked list should be before the header node One node and the next node are both null), so that the entire linked list actually only has a header node, which is used to represent an empty linked list. The second construction method receives a Collection parameter c, calls the first construction method to construct an empty linked list, and then adds all the elements in c to the linked list through addAll. Look at the content of addAll.


public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
 // index参数指定collection中插入的第一个元素的位置
public boolean addAll(int index, Collection<? extends E> c) {
    // 插入位置超过了链表的长度或小于0,报IndexOutOfBoundsException异常
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
    Object[] a = c.toArray();
    int numNew = a.length;
    // 若需要插入的节点个数为0则返回false,表示没有插入元素
    if (numNew==0)
        return false;
    modCount++;
    // 保存index处的节点。插入位置如果是size,则在头结点前面插入,否则获取index处的节点
    Entry<E> successor = (index==size ? header : entry(index));
    // 获取前一个节点,插入时需要修改这个节点的next引用
    Entry<E> predecessor = successor.previous;
    // 按顺序将a数组中的第一个元素插入到index处,将之后的元素插在这个元素后面
    for (int i=0; i<numNew; i++) {
    // 结合Entry的构造方法,这条语句是插入操作,相当于C语言中链表中插入节点并修改指针
        Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
        // 插入节点后将前一节点的next指向当前节点,相当于修改前一节点的next指针
        predecessor.next = e;
        // 相当于C语言中成功插入元素后将指针向后移动一个位置以实现循环的功能
        predecessor = e;
}
    // 插入元素前index处的元素链接到插入的Collection的最后一个节点
    successor.previous = predecessor;
   // 修改size
    size += numNew;
    return true;
}

In the construction method, the addAll(Collection<? extends E> c) method is called, and in the addAll(Collection<? extends E> c) method, only the size is used as the index parameter to call addAll(int index, Collection<? extends E> c) Method.


private Entry<E> entry(int index) {
      if (index < 0 || index >= size)
           throw new IndexOutOfBoundsException("Index: "+index+
                                               ", Size: "+size);
        Entry<E> e = header;
        // 根据这个判断决定从哪个方向遍历这个链表
       if (index < (size >> 1)) {
           for (int i = 0; i <= index; i++)
               e = e.next;
       } else {
           // 可以通过header节点向前遍历,说明这个一个循环双向链表,header的previous指向链表的最后一个节点,这也验证了构造方法中对于header节点的前后节点均指向自己的解释
           for (int i = size; i > index; i--)
                e = e.previous;
        }
         return e;
    }

Combining the comments in the code above and the knowledge of the two-way circular linked list, it should be easy to understand the content involved in the LinkedList construction method. Let's start analyzing other methods of LinkedList.

add (E and)


public boolean add(E e) {
   addBefore(e, header);
   return true;
}

As can be seen from the above code, the add(E e) method just calls the addBefore(E e, Entry<E> entry) method and returns true.

addBefore(E e,Entry<E> entry)


private Entry<E> addBefore(E e, Entry<E> entry) {
   Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
   newEntry.previous.next = newEntry;
   newEntry.next.previous = newEntry;
   size++;
   modCount++;
   return newEntry;
}

The addBefore(E e, Entry<E> entry) method is a private method, so it cannot be called from an external program (of course, this is a general situation, you can still call it through reflection).

addBefore(E e,Entry<E> entry) first creates the node newEntry of e through the Entry construction method (including the operation of setting its next node to entry and the previous node to entry.previous, which is equivalent to modifying newEntry "Pointer"), and then modify the next reference of the previous node of the newEntry and the previous reference of the next node after the insertion position, so that the reference relationship between the linked list nodes remains correct. Then modify the size and record the modCount, and then return to the newly inserted node.

In summary, addBefore(E e, Entry<E> entry) implements inserting a new node constructed by e before entry. And add(E e) realizes inserting a new node constructed by e before the header node.

add(int index,E e)


public void add(int index, E element) {
    addBefore(element, (index==size ? header : entry(index)));
}

The addBefore(E e, Entry<E> entry) method is also called, but the entry node is determined by the value of index.

Construction method, addAll(Collection<? extends E> c), add(E e), addBefor(E e, Entry<E> entry) methods can construct a linked list and insert a node at a specified position. In order to facilitate understanding, the insertion is given below Schematic diagram of the node.
Analysis of the source code of the Java collection LinkedList
addFirst(E e)


public void addFirst(E e) {
   addBefore(e, header.next);
}

addLast(E e)


public void addLast(E e) {
    addBefore(e, header);
}

Looking at the diagram above, combined with the addBefore (E e, Entry<E> entry) method, it is easy to understand that addFrist (E e) only needs to be inserted before the next element of the header element, that is, before the number one in the diagram. addLast(E e) only needs to be implemented before the header node (because it is a circular linked list, so the previous node of the header is the last node of the linked list) insert the node (after the insertion is after the second node).

clear()


public void clear() {
 Entry<E> e = header.next;
 // e可以理解为一个移动的“指针”,因为是循环链表,所以回到header的时候说明已经没有节点了
 while (e != header) {
     // 保留e的下一个节点的引用
         Entry<E> next = e.next;
         // 接触节点e对前后节点的引用
         e.next = e.previous = null;
         // 将节点e的内容置空
         e.element = null;
         // 将e移动到下一个节点
         e = next;
 }
 // 将header构造成一个循环链表,同构造方法构造一个空的LinkedList
 header.next = header.previous = header;
 // 修改size
     size = 0;
     modCount++;
 }

The comments in the above code are sufficient to explain the logic of this code. It should be noted that the "pointer" mentioned is only a conceptual analogy. Java does not have the concept of "pointer", but only references. In order to facilitate understanding, part Explain that "pointers" are used.

contains(Object o)


public boolean contains(Object o) {
    return indexOf(o) != -1;
}

Just judge the index of o in the linked list. First look at the indexOf(Object o) method.


public int indexOf(Object o) {
    int index = 0;
    if (o==null) {
        for (Entry e = header.next; e != header; e = e.next) {
            if (e.element==null)
               return index;
           index++;
        }
    } else {
        for (Entry e = header.next; e != header; e = e.next) {
             if (o.equals(e.element))
                return index;
            index++;
         }
    }
    return -1;
}

indexOf(Object o) judges whether the element of the node in the o linked list is equal to o, if they are equal, returns the index position of the node in the linked list, if not, puts back -1.

The contains(Object o) method judges whether the object o is contained in the linked list by judging whether the value returned by the indexOf(Object o) method is -1.

element()


public E element() {
    return getFirst();
}

getFirst()


public E getFirst() {
    if (size==0)
        throw new NoSuchElementException();
    return header.next.element;
}

The element() method calls getFirst() to return the element of the first node of the linked list. Why do you need to provide two methods with the same function, as if the name is wrapped? In fact, this is just to be able to call more appropriate method names in different contexts.

get(int index)


public E get(int index) {
   return entry(index).element;
}

The get(int index) method is used to obtain the element of the node at the specified index position. It gets the node through the entry (int index) method. The entry (int index) method traverses the linked list and obtains the node. It has been explained above and will not be stated again.

set(int index,E element)


public E set(int index, E element) {
Entry<E> e = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}

First get the node of the specified index, then keep the original element, then replace it with element, and then return the original element.

getLast()


public E getLast()  {
   if (size==0)
       throw new NoSuchElementException();
    return header.previous.element;
}

The getLast() method is similar to the getFirst() method, except that it gets the element of the previous node of the header node. Because it is a circular linked list, the previous node of the header node is the last node of the linked list.

lastIndexOf(Object o)


public int lastIndexOf(Object o) {
    int index = size;
    if (o==null) {
        for (Entry e = header.previous; e != header; e = e.previous) {
           index--;
           if (e.element==null)
               return index;
       }
    } else {
        for (Entry e = header.previous; e != header; e = e.previous) {
            index--;
           if (o.equals(e.element))
                return index;
        }
    }
    return -1;

}

Because the search is the last index, that is, the position of the last occurrence, a backward traversal method is adopted. Because the back-to-forward traversal is used, the index is assigned the size, and the subtraction operation is performed when the loop body is executed. There are two cases to determine whether it exists, namely null and not null.

offer(E e)


public boolean offer(E e) {
    return add(e);
}

Insert an element at the end of the linked list.

offerFirst(E e)


public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

Insert an element at the beginning of the linked list.
offerLast(E e)


public boolean offerLast(E e) {
    addLast(e);
    return true;
}

Insert an element at the end of the linked list.
The above three methods just call the corresponding add method, and also just provide different method names to be used in different contexts.

peek()


public E peek() {
    if (size==0)
        return null;
    return getFirst();
}

peekFirst()


public E peekFirst() {
    if (size==0)
        return null;
    return getFirst();
}

peekLast ()


public E peekLast() {
    if (size==0)
        return null;
    return getLast();
}

The above three methods are also very simple, just call the corresponding get method.

poll()


public E poll() {
    if (size==0)
        return null;
    return removeFirst();
}

pollFirst()


public E pollFirst() {
    if (size==0)
        return null;
    return removeFirst();
}

pollLast()


public E pollLast() {
    if (size==0)
        return null;
    return removeLast();
}

The poll related methods are to get and remove an element. They are all related to the remove operation.

pop()


public E pop() {
    return removeFirst();
}

push (E and)


public void push(E e) {
    addFirst(e);
}

These two methods correspond to the operation of the stack, that is, popping an element and pushing an element, just calling the removeFirst() and addFirst() methods.
The following focuses on the method of remove related operations.

remove()


public E remove() {
    return removeFirst();
}

**remove(int index)**

public E remove(int index) {
    return remove(entry(index));
}

**remove(Object o)**

public boolean remove(Object o) {
    if (o==null) {
        for (Entry<E> e = header.next; e != header; e = e.next) {
            if (e.element==null) {
                remove(e);
                return true;
            }
        }
    } else {
        for (Entry<E> e = header.next; e != header; e = e.next) {
            if (o.equals(e.element)) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}

**removeFirst()**

public E removeFirst() {
    return remove(header.next);
}

**removeLast()**

public E removeLast() {
    return remove(header.previous);
}

**removeFirstOccurrence()**

public boolean removeFirstOccurrence(Object o) {
   return remove(o);
}

**removeLastOccurence()**

public boolean removeLastOccurrence(Object o) {
    if (o==null) {
        for (Entry<E> e = header.previous; e != header; e = e.previous) {
            if (e.element==null) {
                remove(e);
                return true;
            }
        }
    } else {
        for (Entry<E> e = header.previous; e != header; e = e.previous) {
            if (o.equals(e.element)) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}

Several remove methods eventually call a private method: remove(Entry<E> e), which is just a simple logical difference. Let's analyze the remove(Entry<E> e) method.


private E remove(Entry<E> e) {
    if (e == header)
        throw new NoSuchElementException();
    // 保留将被移除的节点e的内容
 E result = e.element;
 // 将前一节点的next引用赋值为e的下一节点
     e.previous.next = e.next;
     // 将e的下一节点的previous赋值为e的上一节点
     e.next.previous = e.previous;
     // 上面两条语句的执行已经导致了无法在链表中访问到e节点,而下面解除了e节点对前后节点的引用
 e.next = e.previous = null;
 // 将被移除的节点的内容设为null
 e.element = null;
 // 修改size大小
     size--;
     modCount++;
     // 返回移除节点e的内容
     return result;
}

clone()


public Object clone() {
    LinkedList<E> clone = null;
    try {
        clone = (LinkedList<E>) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new InternalError();
    }
     clone.header = new Entry<E>(null, null, null);
     clone.header.next = clone.header.previous = clone.header;
     clone.size = 0;
     clone.modCount = 0;
     for (Entry<E> e = header.next; e != header; e = e.next)
         clone.add(e.element);
     return clone;
 }

Call the clone() method of the parent class to initialize the object linked list clone, construct the clone into an empty two-way circular linked list, and then start adding the next node of the header to the clone one by one. Finally, the clone object is returned.

toArray()


public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    for (Entry<E> e = header.next; e != header; e = e.next)
        result[i++] = e.element;
     return result;
 }

Create an array result with the same size as the LinkedList, traverse the linked list, copy the element element of each node into the array, and return the array.

toArray(T[] a)

Analysis of the source code of the Java collection LinkedList

First judge whether the size of the incoming and outgoing array a is sufficient, and expand if the size is not enough. The launch method is used here, and an array of size is re-instantiated. Then assign the array a to the array result, and traverse the elements added to the result from the linked list. Finally, it is judged whether the length of the array a is greater than size, and if it is greater, the content of the size position is set to null. Return a.

It can be seen from the code that when the length of array a is less than or equal to size, all elements in a are covered, and the content stored in the expanded space is null; if the length of the length of array a is greater than size, then 0 to size- The content at position 1 is overwritten, the element at size is set to null, and the element after size remains unchanged.

Why not operate on the array a directly, and operate on the result array after assigning a to the result array? In addition to Entry,
LinkedList's Iterator
has an internal class: ListItr.
ListItr implements the ListIterator interface, it can be seen that it is an iterator, through which LinkedList can be traversed and modified.
A method to get ListItr object is provided in LinkedList: listIterator(int index).


public ListIterator<E> listIterator(int index) {
    return new ListItr(index);
}

This method simply returns a ListItr object.
There is also a listIterator() method obtained through integration in LinkedList, which just calls listIterator(int index) and passes in 0.
Let's analyze ListItr in detail below.

Analysis of the source code of the Java collection LinkedList
Analysis of the source code of the Java collection LinkedList

Analysis of the source code of the Java collection LinkedList
Analysis of the source code of the Java collection LinkedList
The following is an example of using ListItr.
Analysis of the source code of the Java collection LinkedList
result:
Analysis of the source code of the Java collection LinkedList

LinkedList also has a method to provide Iterator: descendingIterator(). This method returns a DescendingIterator object. DescendingIterator is an internal class of LinkedList.


public Iterator<E> descendingIterator() {
    return new DescendingIterator();
}

The following analysis analyzes the DescendingIterator class in detail.
Analysis of the source code of the Java collection LinkedList

It can be seen from the class name and the above code that this is a reverse Iterator, the code is very simple, they are all called methods in the ListItr class.

Guess you like

Origin blog.51cto.com/15061944/2593723