【2023】Detailed introduction and comparison of ArrayList and LinkedList

1. ArrayList

1 Overview

        ArrayList is a dynamic array that implements the List interface. The so-called dynamic array means that its size is variable. All optional list operations are implemented and all elements including Null are allowed. In addition to implementing the List interface, this class also provides methods to manipulate the size of the array used internally to store the list.

        Each ArrayList instance has a capacity, which is the size of the array used to store the list elements. The default initial capacity is 10. The default initial capacity is 10. As the number of elements in the ArrayList increases, its capacity will continue to grow automatically. Each time an element is added, the ArrayList will check whether an expansion operation is required. The expansion operation will cause the data to be re-copied to the new array. Therefore, if we know the specific business data volume, when constructing the ArrayList, we can specify an initial capacity for the ArrayList. This will reduce the copy problem during expansion. Of course, before adding a large number of elements, the application can also use the ensureCapacity operation to increase the capacity of the ArrayList instance, which can reduce the number of incremental reallocations.

2. Source code analysis

ArrayList implements the List interface, and the bottom layer is implemented using arrays, so its operations are basically based on operations on arrays.

2.1. Constructor

  • ArrayList(): Default constructor, providing an empty list with an initial capacity of 10.
  • ArrayList(int initialCapacity): Constructs an empty list with the specified initial capacity.
  • ArrayList(Collection<? extends E> c): Constructs a list containing the elements of the specified collection, arranged in the order in which the collection's iterator returns them.

    /**
     * 构造一个具有指定初始容量的空列表。
     */
    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);
        }
    }

    /**
     * 构造一个初始容量为 10 的空列表
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    //不会立刻创建数组,会在第一次add()时才会创建
    }

    /**
     * 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 
     * 的迭代器返回它们的顺序排列的。
     */
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

2.2. Commonly used methods

 ArrayList provides add(E e), add(int index, E element), addAll(Collection<? extends E> c), addAll(int index, Collection<? extends E> c), set(int index, E element ) These five methods are used to implement ArrayList addition.

  • add(): single insertion of data
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 验证是否需要扩容
        elementData[size++] = e;   //往数组最后的位置赋值
        return true; 
    }




    public void add(int index, E element) {
        rangeCheckForAdd(index);    //判断索引位置是否正确

        ensureCapacityInternal(size + 1);  // 验证是否需要扩容
         /*
         * 对源数组进行复制处理(位移),从index + 1到size-index。
         * 主要目的就是空出index位置供数据插入,
         * 即向右移动当前位于该位置的元素以及所有后续元素。 
         */
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);  
        //给指定下标的位置赋值
        elementData[index] = element;
        size++;
    }

The most fundamental method in this method is the System.arraycopy() method. The fundamental purpose of this method is to free up the index position for new data to be inserted. Here, the array data needs to be shifted to the right, which is very troublesome and time-consuming. , so it is generally not recommended to add elements in this way. If you need to perform a large number of inserts (intermediate inserts) to a specified middle position, it is recommended to use LinkedList.

  • addAll(): Insert data in batches
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();  //将集合c 转换成数组
        int numNew = a.length;  
        ensureCapacityInternal(size + numNew);  // 扩容(当前集合长度+c集合长度)

        //同上,主要是采用该方法把C集合转为数组后的数据进行复制在插入到当前集合的末尾
        System.arraycopy(a, 0, elementData, size, numNew);   
        size += numNew;
        return numNew != 0;
    }


    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);  //判断下标位置是否正确

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 扩容(当前集合长度+c集合长度)
 
        int numMoved = size - index;
//       如果位置不是集合尾部,则需要先把数据向右移动指定长度(添加数据的长度)
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
//       然后再把数据加入到指定下标位置
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

        This method is nothing more than using the System.arraycopy() method to copy the data in the C collection (first converted to an array) into the elementData array. Here is a brief introduction to System.arraycopy(), because this method will be used extensively below.

        The prototype of this method is: public static void  arraycopy (Object src, int srcPos, Object dest, int destPos, int length). Its fundamental purpose is to copy array elements.

        That is, copy an array from the specified source array, starting from the specified position and ending at the specified position of the target array. Copy the source array src from the srcPos position to the dest array, the copy length is length, and the data is pasted from the destPos position of dest.

  • get(): Find the element with the specified subscript
        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

ArrayList provides get(int index) for reading elements in ArrayList. Since ArrayList is a dynamic array, we can obtain the elements in ArrayList based on the subscripts, so its time complexity is O(1); the storage/retrieval speed is still relatively fast. But if it is a search, inserting and deleting elements will not be very efficient, because they need to traverse the results one by one for comparison. So the time complexity will be O(n).

2.3. Expansion

        In the source code of the new method above, we found that this method exists in each method: ensureCapacity(), which is the expansion method of ArrayList. As mentioned before, ArrayList needs to perform capacity detection and judgment every time a new element is added. If the number of elements after adding an element exceeds the capacity of ArrayList, an expansion operation will be performed to meet the needs of the new elements.

        So if we clearly know the amount of business data or need to insert a large number of elements, we can directly specify the capacity when creating the collection, or manually increase the capacity of the ArrayList instance through ensureCapacity to reduce the number of incremental reallocations.

  • Source code implementation:
   //minCapacity :所需的最小容量
 private void grow(int minCapacity) {
        // 集合长度
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);   //增加原来的1.5倍
//        判断所需要的最小容量大于增加原来的1.5倍的长度,则容量扩大为minCapacity
        if (newCapacity - minCapacity < 0)  
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)  //判断是否会大于虚拟机的最大容量
            newCapacity = hugeCapacity(minCapacity);
        // 拷贝数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

The expansion logic is: 

  • When calling ArrayList() without parameters, the initial capacity is 0; when calling with parameters, it will be the parameter size you gave; when calling a collection, it will be the size of the collection.
  • When elements are added, the capacity will be expanded. An array with a capacity of 10 will be expanded. When the set elements reach 10, the capacity will be expanded for the second time. The second expansion will be expanded to 1.5 times the length of the previous array (the underlying logic is: 15>>1=7+15=22) First shift right and then add the original array length,

二、LinkedList

1 Overview

Linkedlist is a dynamic array based on linked list (double linked list):

  • Can be operated as a stack (last in, first out), queue (first in, first out), or deque.
  • Adding and deleting data is efficient and only requires changing the pointer. However, the average efficiency of accessing data is low and requires traversing the linked list. Not synchronized, thread-unsafe.
  • Supports null elements, order, and elements can be repeated
  • Don't use an ordinary for loop to traverse LinkedList, use an iterator or foreach loop (the principle of foreach loop is iterator) to traverse LinkedList:
  • This method finds data directly according to the address, which will greatly improve the efficiency of traversing LinkedList.

2. One-way linked list and two-way linked list

2.1. One-way linked list

  • Each element of the linked list is called a node (Node)
  • Non-continuous and non-sequential storage structure on physical storage units

data: data domain, stores data

next: pointer field, pointing to the storage location of the next node

Time complexity analysis:

Query operation

  • Only when there is no need to traverse the linked list, and the time complexity is O(1) ;
  • Querying other nodes requires traversing the linked list, and the time complexity is O(n) ;

Add and delete time complexity

  • Only when adding and deleting the head node, there is no need to traverse the linked list, and the time complexity is O(1);
  • Adding or deleting other nodes requires traversing the linked list to find the corresponding node before adding or deleting the node. The time complexity is O(n);

 2.2. Doubly linked list

The doubly linked list, as its name implies, supports two directions:

  • Each node has more than one subsequent pointer next pointing to the subsequent node.
  • There is a predecessor pointer prev pointing to the previous node.

When you need to get the previous node, you only need to call prev, and to get the next node, you only need to call the next pointer.

Compare one-way linked list:

  • Doubly linked lists require two additional spaces to store the addresses of subsequent nodes and predecessor nodes.
  • Supports two-way traversal, which makes doubly linked list operations more flexible

Time complexity analysis:

Inquire:

  • The time complexity of querying the head and tail nodes is O(1)
  • The average query time complexity is O(n)
  • The time complexity of finding the predecessor node for a given node is O(1);

Additions and deletions:

  • The time complexity of adding and deleting head and tail nodes is O(1)
  • The time complexity of adding and deleting other nodes is O(n)
  • The time complexity of adding and deleting a given node is O(1);

3. The difference between ArrayList and LinkedList

1. Comparison of underlying data storage structures

  •  ArrayList is a data structure based on dynamic arrays, stored as continuous memory
  • LinkedList is a data structure based on a doubly linked list, and its memory storage is non-continuous.

2. Operation data efficiency:

  • Search: Search comparison ArrayList Because the bottom layer is based on array continuity, it implements an interface called RandomAccess, so that when searching, it will search through the addressing formula based on the subscript; while the bottom layer of LinkedList is a doubly linked list, this interface is not implemented. , when searching for elements, you can only use the next() iterator to iterate and search one by one.
  • Additions and deletions: The tail insertion performance of ArrayList is faster, while the insertion performance of other parts will be slower, because when inserting at the previous position, each array element must be moved backward; while LinkedList is inserted at the head and tail of the linked list structure. When deleting, there is no need to search and locate. When deleting and adding, other elements are not involved. You only need to modify the pointer. Everything is faster. However, if you delete or modify an intermediate element, you need to first locate the position of the element to be modified, and positioning It is more time consuming, and all performance will be slower.
  • But if you are looking for an unknown index, the ArrayList also needs to be traversed, and the time complexity will be O(n)

3. Memory space occupied:

  • Because the bottom layer of ArrayList is an array, the memory is continuous, saving memory.
  • LinkedList is a doubly linked list that needs to store data and two pointers, and takes up more memory.
  • Moreover, ArrayList can take advantage of the locality principle of cpu buffering. When loading, all adjacent elements will be loaded at once into the cpu buffer. Memory reading can be read from the cpu buffer first, which can effectively improve Execution efficiency, and LinkedList is a linked list. The elements are only pointed to by pointers and may not be adjacent, so this feature cannot be effectively used.

4. Thread safety:

  • Neither ArrayList nor LinkedList are thread safe
  • If you need to achieve thread safety, you can create packages through Collections.synchronizedList()
    • Collections.synchronizedList(new LinkedList<>())
    • Collections.synchronizedList(new ArrayList<>())
 
 
 
 
 
 

Guess you like

Origin blog.csdn.net/weixin_52315708/article/details/131853639