java Set Analysis (9): LinkedList

Posted by riddlejk on Sat, 18 May 2019 10:35:31 +0200

Previously, we analyzed ArrayList. We know that ArrayList is implemented by arrays and traverses quickly, but when inserting and deleting, we need to move the elements behind, which is slightly less efficient. In this article, let's look at LinkedList, which is implemented by a two-way linked list. When inserting and deleting linkedList, we only need to change the pointers of the front and back nodes.

What is LinkedList

LinkedList is a two-way linked list inherited from AbstractSequential List. It can also be operated as a stack, queue, or double-ended queue.
LinkedList implements the List interface and can perform queue operations on it.
LinkedList implements the Deque interface, which can use LinkedList as a double-ended queue.
LinkedList implements Cloneable interface, which covers the function clone(), and can clone.
LinkedList implements the java.io.Serializable interface, which means that LinkedList supports serialization and can be transmitted through serialization.
LinkedList is asynchronous.

Membership variables of LinkedList

//size of linked list 
transient int size = 0;
//Point to the first node  
transient Node<E> first;
//Point to the last node
transient Node<E> last;

Node

Node is the static inner class of LinkedList, and each node points to the former node and the latter node, which is the two-way linked list node.

    private static class Node<E> {
        // Values contained in the current node
        E item;
        //Next node
        Node<E> next;
        //Last node
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

Constructor of LinkedList

 //Default constructor to create an empty list
 public LinkedList() {
 }

  //Create a LinkedList with "c Collection"
  public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
 }

LinkedList Method

1. Several key internal approaches

  //Insert into the head
  private void linkFirst(E e) {
        //Head Node Assignment to f
        final Node<E> f = first;
        //Create a new node, and the header pointer points to the newly created node newNode
        final Node<E> newNode = new Node<>(null, e, f);
        //New Node assigns a value to the node first
        first = newNode;
        //If f is null, it means that there were no nodes in the previous list, so last points to newNode.
        if (f == null)
            last = newNode;
        else
          //The head of the first node (now the second) points to the new one.
            f.prev = newNode;
        size++;
        modCount++;
    }

   //Add to the tail
    void linkLast(E e) {
       //Endpoint assignment to f
        final Node<E> l = last;
         //Create a new node, and the endpoint pointer points to the newly created node newNode
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
         //If f is null, indicating that there were no nodes in the previous list, then the new node is the first node.
        if (l == null)
            first = newNode;
        else
         //The tail of the original tail node points to the new tail node
            l.next = newNode;
        size++;
        modCount++;
    }

 //Insert an element before the specified node
 void linkBefore(E e, Node<E> succ) {
        // Gets a node preceding the specified node succ
        final Node<E> pred = succ.prev;
        //Create a new node with the head pointing to the node in front of succ and the tail pointing to the succ node with the data e
        final Node<E> newNode = new Node<>(pred, e, succ);
        //Let the node in front of succ point to the new node
        succ.prev = newNode;
        //If the node in front of succ.prev is empty, that means succ is the first node, then the new node will become the first node now.
        if (pred == null)
            first = newNode;
        else
        //If there are nodes ahead, point the new node to pred.next
            pred.next = newNode;
        size++;
        modCount++;
    }

  //Delete the header node and return the value on that node
  private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        // Get the value of the node and assign it to the element 
        final E element = f.item;
        //Get a node behind the header node
        final Node<E> next = f.next;,
        //Make the data on the head node empty and the tail point to null
        f.item = null;
        f.next = null; // help GC
        //Set a node behind the header node to the first
        first = next;
        //If the node behind the head node is null, there is only one node in the list.
        if (next == null)
            last = null;
        else
        //If there is more than one node in the list, set next.prev to null
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    //Delete the tail node and return the value on that node
 private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        // Get the value of the node and assign it to the element 
        final E element = l.item;
        // Get a node in front of the tail node
        final Node<E> prev = l.prev;
        //Make the data on the tail node empty and the head point to null
        l.item = null;
        l.prev = null; // help GC
        //Set one node in front of the tail node to the last one
        last = prev;
        //If the node in front of the tail node is null, there is only one node in the list.
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

//Delete a specified node and return the value on that node
E unlink(Node<E> x) {
        // assert x != null;
        // Get the value of the node and assign it to the element 
        final E element = x.item;
        //Gets a node behind the node
        final Node<E> next = x.next;
        //Get a node in front of that node
        final Node<E> prev = x.prev;

        //If there are no nodes in front, x is the first
        if (prev == null) {
            first = next;
        } else {
           //If there is a node in front, let the former node cross X and point directly to the node behind x
            prev.next = next;
            x.prev = null;
        }
       //If there are no nodes later, say x is the last node
        if (next == null) {
            last = prev;
        } else {
          //There are nodes at the back, so that the latter node points to the one in front of x.
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }

    //Gets the node at the specified location
    Node<E> node(int index) {
        // assert isElementIndex(index);
        // Size >> 1. Move one representative to the right by dividing it by 2. Here we use a simple dichotomy to determine the distance between index and list.
        if (index < (size >> 1)) {
            Node<E> x = first;
            // If index is less than half of size, traverse backwards from the head.
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
           // If the index is greater than half of size, the previous traversal is backward from the tail.
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

2. Adding Elements

 //Add elements to the head
 public void addFirst(E e) {
        linkFirst(e);
 }

 //Add elements to the tail
 public void addLast(E e) {
        linkLast(e);
 }

 //Add elements to the tail
 public boolean add(E e) {
        linkLast(e);
        return true;
 }

 //Adding elements of a collection
 public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
 }

 //Add a collection at the specified location
 public boolean addAll(int index, Collection<? extends E> c) {
        //Check whether index is out of bounds
        checkPositionIndex(index);
        //Convert set c to an array
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        //Create two nodes, pointing to the nodes before and after the insertion position, respectively
        Node<E> pred, succ;
        //Index = size indicates that it's the tail, and we need to add elements to the tail.
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        //Get the current node through node(index) and give succ, then get the previous node through succ.prev and assign it to pred 
            succ = node(index);
            pred = succ.prev;
        }

        //Traversing through the array of contents to be added
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            //If pred is empty, the new one is the header node
            if (pred == null)
                first = newNode;
            else
             //Point newNode to pred.next
                pred.next = newNode;
            pred = newNode;
        }
       //If succ is null, indicating that the place to insert is the tail, pred is pointed to last
        if (succ == null) {
            last = pred;
        } else {
          //Otherwise pred points to the following element
            pred.next = succ;
            succ.prev = pred;
        }
      //Increased number of elements
        size += numNew;
        modCount++;
        return true;
    }

    //Adding elements at specified locations
    public void add(int index, E element) {
        //Check whether index is out of bounds
        checkPositionIndex(index);
        //Index = size, indicating that it is already the tail, adding elements directly to the tail
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

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

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

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


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

3. Delete elements

  //Delete the header node and return the value on that node
  public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

//Delete the tail node and return the value on that node
public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
//Delete the node containing the specified element
public boolean remove(Object o) {
        if (o == null) {
            //Ergodic deletion
            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;
    }

//Clean up the bi-directional list
public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        //Traversing backwards from the first node one by one
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        //Head and tail nodes are null
        first = last = null;
        size = 0;
        modCount++;
  }

 //Delete the node at the specified location
 public E remove(int index) {
         //Check whether index is out of bounds
        checkElementIndex(index);
        return unlink(node(index));      
 }

 //Delete the header node and return the deleted data
 public E remove() {
        return removeFirst();
 }

 //Delete the header node and return the deleted data
 public E pop() {
        return removeFirst();
 }

 //Delete the first o node in the list 
 public boolean removeFirstOccurrence(Object o) {
        return remove(o);
 }

 //Reverse traversal to delete the node that first appears o  
 public boolean removeLastOccurrence(Object o) {
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

3. Query, modify, whether to include elements

//Elements returning to the header node
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
}

//Returns the element value of the tail node
public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
}

//Gets the element value of the node at the specified index location
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
}

//Returns the element value of the header node    
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
}

//Returns the element value of the header node
public E element() {
        return getFirst();
}

//Retrieve the header node, return null if empty, return its element value if not empty and delete the header node  
public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
}

//Returns the element value of the header node
public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
}

//Returns the element value of the tail node
public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
}

//Retrieve the header node, return null if empty, return its element value if not empty and delete the header node 
public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
}

//Retrieve the tail node, return null if empty, return its element value if not empty, and delete the tail node 
public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
}

//Replace the element value of the node at the specified index location  
public E set(int index, E element) {
        checkElementIndex(index);
        //Returns the node at the specified index location
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
}

//Determine whether the list contains element o  
public boolean contains(Object o) {
        return indexOf(o) != -1;
}

//Returns the location of o for the first time in the list, or - 1 if it does not exist 
public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            //Traverse all nodes backwards from the beginning
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
}

//Reverse traversal, return to the first place where o appears, and return to -1 if none exists.  
public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
             //Traverse all nodes forward from the tail node
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
 }

4. Array Conversion

public Object[] toArray() {
        //Create a new array of size
        Object[] result = new Object[size];
        int i = 0;
        //Traverse all nodes
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
}

public <T> T[] toArray(T[] a) {
        if (a.length < size)
         //Create an array of strings a of size, of the same type as oldArray, which can also be used as an Object object in Java 
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size)
            a[size] = null;

        return a;
}

5. iterator

//Inverse iterator calls descendingIterator() to get a backward-forward traversal of the LinkedList object.
public Iterator<E> descendingIterator() {
        return new DescendingIterator();
}

/**
 * Adapter to provide descending iterators via ListItr.previous
 */   
private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
}
 //A list traversal, which returns a list traversal that starts at a specified location by calling listIterator(int index).
 public ListIterator<E> listIterator(int index) {
        //Check whether index is out of bounds
        checkPositionIndex(index);
        //Returns a list traversal from the specified location
        return new ListItr(index);
    }

    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned = null;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }
        //Determine if there is another node 
        public boolean hasNext() {
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }
        //Judging whether there is a previous node
        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

        public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (modCount == expectedModCount && nextIndex < size) {
                action.accept(next.item);
                lastReturned = next;
                next = next.next;
                nextIndex++;
            }
            checkForComodification();
        }
//Similar to ArrayList, it uses an attribute modCount to determine whether the list is structurally changed after the traversal is obtained, and if so, throws a Concurrent ModificationException exception.
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

6.LinkedList traversal

public class Test {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // Traversing LinkedList through Iterator
        iterator(getLinkedListData());

        // Traversing LinkedList through Quick Random Access
        for1(getLinkedListData());

        // Traversing LinkedList through a forEast loop
        for2(getLinkedListData());

    }

    private static LinkedList getLinkedListData() {
        LinkedList llist = new LinkedList();
        for (int i = 0; i < 100000; i++)
            llist.addLast(i);

        return llist;
    }

    /**
     * Traversing LinkedList through an iterator
     */
    private static void iterator(LinkedList<Integer> list) {

        // Record start time
        long start = System.currentTimeMillis();
        Iterator iter = list.iterator(); 
        while (iter.hasNext()) {
            iter.next();
        }

        // End time of recording
        long end = System.currentTimeMillis();
        long interval = end - start;
        System.out.println("Iterator traversal:" + interval + " ms");
    }

    /**
     * Traversing LinkedList through Quick Random Access
     */
    private static void for1(LinkedList<Integer> list) {

        // Record start time
        long start = System.currentTimeMillis();

        int size = list.size();
        for (int i = 0; i < size; i++) {
            list.get(i);
        }
        // End time of recording
        long end = System.currentTimeMillis();
        long interval = end - start;
        System.out.println("Quick random access:" + interval + " ms");
    }

    /**
     * Traversing LinkedList through the forEast loop
     */
    private static void for2(LinkedList<Integer> list) {

        // Record start time
        long start = System.currentTimeMillis();

        for (Integer integ : list){

        }


        // End time of recording
        long end = System.currentTimeMillis();
        long interval = end - start;
        System.out.println("forEach Circulation:" + interval + " ms");
    }
}

Operation results:

As can be seen from the above results, iterators or forEach are most efficient when traversing LinkedList to get element values. Quick random access is inefficient, so it's better not to traverse LinkedList through random access.

The difference between ArrayList and LinkedList is:

ArrayList

  • The bottom layer is based on arrays, which supports random access, high efficiency of query and data acquisition.

  • After inserting or deleting an element index, elements are moved (backward or forward), so adding/deleting data is inefficient.

  • With capacity, each time the threshold is reached, it needs to be expanded, and at the end of the list a certain capacity space will be reserved.

LinkedList

  • The bottom layer is based on a double-ended circular list, which does not support random access. Adding or deleting elements will only affect the two nodes around it. The cost is very low, so it is efficient to add or delete elements.

  • It can only traverse sequentially and can not get elements according to the index, so the query efficiency is not high.

  • No fixed capacity, no expansion; but it needs more memory, because each node needs to store the information of the front and back nodes, so every element of it needs to consume considerable space.

Conclusion:

ArrayList and LinkedList have their own advantages and disadvantages, so when we need to find elements frequently, we can choose ArrayList, which is more efficient. If we need to add or delete lists frequently, it is better to choose LinkedList.

Thanks:

http://blog.csdn.net/u010370082/article/details/45046755

http://blog.csdn.net/xujian_2014/article/details/46806999

Topics: Java less Attribute