Java collection: on LinkedList

Posted by jakem on Thu, 03 Feb 2022 16:29:01 +0100

🚩 Java collection: on LinkedList

📚 1. Use of LinkedList set

✒️ 1.1 simple use of LinkedList set

import java.util.LinkedList;

public class TestDemo {
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(11);
        list.add(22);
        list.add(33);
        list.add(11);
        list.add(null);

        for (Integer value : list) {
            System.out.print(value + " ");
        }

    }
}

Operation results:

🖊️ 1.2 LinkedList set features

1. The data is in order of insertion
2. Data can be inserted repeatedly
3. null values can be stored
4. The bottom layer adopts the data structure of two-way linked list

📕 2. Study the implementation of LinkedList through JDK source code

📫 2.1 inheritance

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

Class structure diagram:

① LinkedList inherits the AbstractSequentialList abstract class. AbstractSequentialList inherits from AbstractList and implements common methods to facilitate the direct reuse of subclasses.
② LinkedList also implements List, Cloneable and Java io. Serializable interface, which can realize cloning and serialization.
③ deque interface is implemented. This interface is a two terminal Queue interface, which inherits from the Queue interface. Therefore, LinkedList is also the implementation class of the Queue interface, and queues and stacks can be realized with the help of this Queue.

📪 2.2 properties and default values

    //Number of valid data
    transient int size = 0;
    //Head node position
    transient Node<E> first;
	//Tail node position
    transient Node<E> last;

//Node class
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;
        }
    }

From the Node class, we can see that the underlying layer of LinkedList is a data structure of two-way linked list.

📬 2.3 constructor

non-parameter constructor

public LinkedList() {
    }

Parameterized constructors parameterized constructors call nonparametric constructors and bulk add functions

 public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
 }

📭 2.4 common methods source code analysis

📑 2.4.1 add: add element

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

	void linkLast(E e) {
        final Node<E> l = last;
        //The new node takes the tail node of the linked list as the precursor, and inserts the new node into the tail of the linked list
        final Node<E> newNode = new Node<>(l, e, null);
        //Update the tail node and take the new node as the tail node
        last = newNode;
        
        if (l == null)
        	//If the first node is currently inserted, point the head node to the new node. At this time, first = last = newNode
            first = newNode;
        else
        	//The current inserted node is not the first node. The next field of the tail node of the original linked list points to the new node
            l.next = newNode;
        size++;//Number of effective elements + 1
        modCount++;
    }

📄 2.4.2 remove: delete element

public boolean remove(Object o) {
		//Determine whether the deleted element is null
        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;
    }

	//Delete element logic
	 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;

		//Determine whether the deleted element is the first element
        if (prev == null) {
        //The deleted element is the first element, and the header node points to the second element
            first = next;
        } else {
        //The deleted element is not the first element
        //The successor of the previous node of the deleted element points to the next node of the deleted element
        //The precursor of the deleted element is set to null
            prev.next = next;
            x.prev = null;
        }
		
		//Determine whether the deleted element is the last element
        if (next == null) {
        //The deleted element is the last element, and the tail node points to its previous node
            last = prev;
        } else {
        //The deleted element is not the last element
        //The precursor of the last node of the deleted element points to the previous node of the deleted element
        //The successor of the deleted element is set to null
            next.prev = prev;
            x.next = null;
        }

		//Set the item of the deleted node to null
        x.item = null;
        size--;//Number of valid elements - 1
        modCount++;
        return element;
    }

The precursor, successor and item of the Node are set to null to facilitate Node recycling

📃 2.4.3 get: get elements

    public E get(int index) {
    	//Check the validity of the subscript
        checkElementIndex(index);
        return node(index).item;
    }

	 private void checkElementIndex(int index) {
	 //If the subscript is illegal, a subscript out of bounds exception is thrown
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
 	
 	//Check whether the subscript is between 0 and the limited range of size - 1
	 private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

	  Node<E> node(int index) {
        // assert isElementIndex(index);
		//Use the binary search method to find elements
		//If the subscript is in the first half of size/2, look from front to back
		//If the subscript is in the second half of size/2, look from back to front
        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;
        }
    }

🃏 3. Comparison between ArrayList and LinkedList

Bottom structureEfficiency of addition and deletionEfficiency of modification
ArrayListVariable arrayLower array expansionhigher
LinkedListBidirectional linked listHigher, added through linked listLower

How to select ArrayList and LinkedList:

1. Select ArrayList if we have many operations to change the query
2. If we have many additions and deletions, select LinkedList
3. Generally speaking, in the program, 80% ~ 90% are queries, so ArrayList will be selected in most cases
4. In a project, it can be selected flexibly according to the business. One module uses ArrayList and the other module uses LinkedList

Topics: Java Back-end