Verify and analyze the difference between LinkedList and ArrayList by analyzing uml diagram and jdk source code

Posted by sneamia on Tue, 07 Dec 2021 09:05:50 +0100

1, Overview

Recently, I want to change my job. I interviewed several people before. I feel that both sides don't see each other very much. I'm 40 years old. It's almost the end of the year. I'd better do some preparatory work. It's hard to find a job.
I was not ready to find information on the Internet. I happened to see the "Huawei Daniel thoroughly explains 100 required questions for Java interview" at station B, which is quite suitable for my appetite. I think it's not deep enough to deepen my impression, so I almost wanted to dig deeply.
This paper selects the difference between ArrayList and LinkedList for specific analysis.

2, Introduction to great God

First of all, the screenshot of the great God's introduction to the difference between the two is as follows.

In summary, the great God mainly talked about the following points:

  1. Different storage methods
    ArrayList: continuous memory storage based on dynamic array.
    LinkedList: Based on linked list, it can be stored in scattered memory.
  2. Good at operation
    ArrayList: suitable for subscript access (random access), tail interpolation and insertion of specified capacity.
    LinkedList: suitable for data insertion and deletion.
  3. Not good at operation
    ArrayList: for capacity expansion beyond the length, you need to create a new array, and then copy the old data to the new array; Not tail insertion, involving the movement of data elements.
    LinkedList: it is not suitable for query. You can't use a for loop. Don't try to return the element index with indexOf.

3, UML analysis

Create a new project in IDEA, find External Libraries, find the jdk, expand rt.jar, right-click util under java, and select diagrams - > show diagram... Option.

Select the Java Class Diagrams option to open the class diagram of the java.util package.

Next, select other contents not related to this time in the class diagram, press Delete to delete them, and only the contents related to the List are retained. The final results are as follows:

The inheritance and implementation of the two can be analyzed first.

  1. First, both implement the List interface. The List interface inherits the Collection interface, and the Collection inherits the iteratable interface.
  2. ArrayList also inherits the RandomAccess interface, but when the RandomAccess interface is opened, it is found that the interface is empty and does not implement any content. We will ignore it for the time being.
package java.util;

public interface RandomAccess {
}

Both inherit the AbstractList class. The difference is that LinkedList inherits AbstractList through AbstractSequentialList, and ArrayList directly inherits to AbstractList.
Comparative analysis of AbstractSequentialList and LinkedList.

  • AbstractSequentialList implements six methods: get, set, add, remove, addAll and Iterator, and defines an abstract method listIterator.
  • LinkedList rewrites the methods implemented by AbstractSequentialList.

4, Source code analysis

This section analyzes the differences from the source code according to the introduction of the reference God.

(1) Storage mode

1. Analyze ArrayList for array storage

Viewing the ArrayList, you can see the following code:

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

It can be seen that the member variable elementData is initialized according to different parameters during the creation of ArrayList.
The elementData is located as an Object data in the code.

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

Looking at the comments on elementData, we can also see that elementData is an array used by ArrayList to store elements. The capacity of ArrayList is the length of elementData.
It can be seen that the contents stored in ArrayList are indeed arrays.

2. Analyze ArrayList as a dynamic array

View the Add method of ArrayList

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

View ensureCapacityInternal

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
        private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

Finally, trace to the grow th function.

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

You can see that when inserting new data, ArrayList will expand the length of the array if the length of the original array is not enough.

3.LinkedList

Similarly, check the constructor code of the LInkedList:

/**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

Since there is no code in the parameterless constructor, let's look at another constructor for addAll © Analysis.

/**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element
     *              from the specified collection
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

The core content is to traverse the contents of the Collection parameter, establish nodes in turn, and then associate the upper and lower elements through the node's next and prev.
Where Node is the internal class of LinkedList.

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

There are three parameters. item is the element itself, next points to the next element, and prev points to the previous element.
It can be seen that the contents stored in LinkedList are linked lists.

(2) Good at operation

1. ArrayList access

View the get method of ArrayList. In the two-step operation, first check whether the subscript exceeds, and directly return the subscript data.

    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

Check the add code. If the capacity of ArrayList is enough, add is inserted directly at the end. Therefore, the tail interpolation method with sufficient capacity is very efficient.

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

2.LinkedList

View Add method

   /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

Look at the two functions inside, linkLast (link to the end) and linkBefore (link to the front). From the meaning of the function, I think it is very efficient. In the actual process, you can also create a node object for the operation process, and then assign relevant values.

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        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++;
    }

(3) Not good at operation

1.ArrayList is super long and expanded.

You can view the contents of "analyze ArrayList as dynamic array". During the insertion of ArrayList, if the length exceeds the set length, it needs to be expanded (the expansion process is to create a new array, and then copy the old array to the new array). If it is not tail insertion, you also need to move the element.

2. LinkedList

It is not suitable for the for loop. Check the get (I) - > node (index) method. It is found that the contents of the linked list need to be traversed during each get process. Therefore, if you loop LinkedList, the number of iterations is quite a lot.

    /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
/**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

Don't try to return the element index with indexOf. Similarly, you can see from the code that the indexOf function of LinkedList also needs to traverse the linked list.

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     *
     * @param o element to search for
     * @return the index of the first occurrence of the specified element in
     *         this list, or -1 if this list does not contain the element
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            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;
    }

4, Conclusion

Through the above rounds of analysis, we also have a certain understanding of the total number of LinkedList and ArrayList. Later, we will pay more attention when writing code. We will no longer use LinkedList or ArrayList as soon as we come up. We should make overall arrangements according to the actual needs.

Topics: Java Interview arraylist LinkedList