ArrayList detailed explanation and expansion source code analysis

Series Article Directory

Self-implementation of dynamic array (ArrayList) of linear table - Programmer Sought


Article directory

  • foreword
  • 1. Analysis of the three construction methods of ArrayList
  • 2. How does ArrayList expand? (Source code analysis)
  • 3. Common methods of ArrayList
  • Four, three kinds of traversal of ArrayList
  • 5. Defects of ArrayList

foreword

In the collection framework, ArrayList is a common class that implements the List interface. The specific frame diagram is as follows:
1. ArrayList implements the RandomAccess interface, indicating that ArrayList supports random access
2. ArrayList implements the Cloneable interface , indicating that ArrayList can be cloned
3. ArrayList implements the Serializable interface, indicating that ArrayList supports serialization
4. Unlike Vector , ArrayList is not thread-safe and can be used in a single thread. In multi-threading, you can choose Vector or CopyOnWriteArrayList
5. The bottom layer of ArrayList is a continuous space and can be dynamically expanded. It is a dynamic type sequence table

1. Analysis of the three construction methods of ArrayList

method explain
ArrayList ()
No-argument constructor
ArrayList (Collection<? extends E> c)
Build ArrayList from other Collections
ArrayList (int initialCapacity)
Specify the initial capacity of the sequence table
 public static void main(String[] args) {
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        ArrayList<Integer> arrayList2 = new ArrayList<>(12);
        arrayList1.add(1);
        ArrayList<Integer> arrayList3 = new ArrayList<>(arrayList1);
        System.out.println(arrayList3);
    }

 Note: When using the constructor of ArrayList (Collection<? extends E> c), because this is the upper bound of wildcards, note that the type passed in must be E or a subclass of E.

2. How does ArrayList expand? (Source code analysis)

First look at the constructor without parameters:

 DEFAULT_CAPACITY = 10 The default capacity is 10; transient Object[] elementData, similar to the array behind the sequence table; private int size , size is the number of valid elements, which is 0 at this time, indicating that the length of DEFAULTCAPACITY_EMPTY_ELEMENTDATA is 0, this construction method does not Allocate array memory.

So now there is a question, why is the length of DEFAULTCAPACITY_EMPTY_ELEMENTDATA being 0 and the default capacity being 10 contradictory? There is no size at this time, so how did you put it in when adding data for the first time?

Next, we continue to click to enter the add method:

 When describing the storage element, it is placed after the last element by default. But a ensureCapacityInternal(size + 1) method is used here, we are clicking to enter,

At this time, size is 0. When size+1 is passed in, minCapacity is 1; then there are two more methods. The return value of the calculateCapacity(elementData, minCapacity) method should be used as the parameter of the ensureExplicitCapacity() method. .

Then we click to enter the calculateCapacity(elementData, minCapacity) method. Here, as the name implies, it is to calculate the capacity. At this time, minCapacity is 1, because the construction method without parameters is called at this time, then elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA at this time.

At this time, if the If statement is satisfied, then the maximum value will be calculated. At this time, minCapacity is 1, DEFAULT_CAPACITY = 10, so when add is called for the first time, 10 will be returned at this time. Then at this time, the return value of 10 will be used as the parameter of ensureExplicitCapacity(), then we click to enter the inside of this method:

At this time, the minCapacity is 10, and the length of the elementData array is 0. If 10-0 > 0, then the grow function will be called to pass 10. Let's click grow again to enter this function:

 At this time, oldCapacity and newCapacity are calculated to be 0, because 0 - 10 < 0, then newCapacity = minCapacity = 10. Next, look at the following if statement:

At this time, MAX_ARRAY_SIZE is the maximum value of int minus 8. At this time, the if statement cannot come in and continues to execute, then the array is actually allocated memory at this time.

elementData = Arrays.copyOf(elementData, newCapacity);

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

Equivalent to ensureCapacityInternal(size + 1) this method is finished, the capacity of the array is 10, and then the elements can be put in. So it can be concluded that the premise is that the constructor without parameters is called. When adding for the first time, the default capacity is 10.

So how does it scale? Like when putting the 11th element?

Next, we continue to look at the grow function.

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //按照1.5倍方式扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小 
        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);
    }

 int newCapacity = oldCapacity + (oldCapacity >> 1); In fact, this code is expanding, and it is 1.5 times the expansion. 

In fact, the construction method with parameters, how much the initial value is given, then what is the initial capacity:

The construction method of ArrayList (Collection<? extends E> c) is actually an array copy:

 summary:

1. Detect whether expansion is really needed, if it is to call grow to prepare for expansion
2. Estimate the size of the required storage capacity: The initial estimate is to expand the capacity according to 1.5 times the size; if the size required by the user exceeds the estimated size by 1.5 times, the capacity will be expanded according to the size required by the user; before the actual expansion, check whether the capacity can be expanded successfully to prevent it from being too large The expansion fails.

3. Common methods of ArrayList

method explain
boolean add (E e)
tail plug e
void add (int index, E element)
Insert e at index position
boolean addAll (Collection<? extends E> c)
tail insert element in c
E remove (int index)
remove the element at index position
boolean remove (Object o)
delete the first o encountered
E get (int index)
Get the subscript index position element
E set (int index, E element)
Set the subscript index position element to element
void clear ()
empty
boolean contains (Object o)
Determine if o is in a linear table
int indexOf (Object o)
Returns the subscript where the first o is located
int lastIndexOf (Object o)
Returns the subscript of the last o
List<E> subList (int fromIndex, int toIndex)
Intercept part of the list

 Here we need to pay attention to the method List<E> subList (int fromIndex, int toIndex):

 public static void main(String[] args) {
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        arrayList1.add(1);
        arrayList1.add(2);
        arrayList1.add(3);
        arrayList1.add(4);
        arrayList1.add(5);
        List<Integer> list = arrayList1.subList(1,3);
        System.out.println(list);//2,3
        list.set(0,99);
        System.out.println(arrayList1);// 1,99,3,4,5
        System.out.println(list);//99,3
}

This will find that if the data in the list is modified, the data in the arraylist will also be modified. Take a look at a memory map to see what's going on.

 

Four, three kinds of traversal of ArrayList

ArrayList can be traversed in three ways: for loop + subscript , foreach , using iterator

 public static void main(String[] args) {
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        arrayList1.add(1);
        arrayList1.add(2);
        arrayList1.add(3);
        arrayList1.add(4);
        arrayList1.add(5);

        int size = arrayList1.size();
        for (int i = 0; i < size; i++) {
            System.out.print(arrayList1.get(i)+" ");
        }
        System.out.println();
        for (int x : arrayList1) {
            System.out.print(x+" ");
        }
        System.out.println();

        Iterator<Integer> it =  arrayList1.iterator();
        while (it.hasNext()) {
            System.out.print(it.next()+" ");
        }
}

5. Defects of ArrayList

The bottom layer of ArrayList uses arrays to store elements. Since the bottom layer is a continuous space, when inserting or deleting elements at any position in ArrayList , it is necessary to move the entire sequence elements forward or backward . The time complexity is O(n ) , the efficiency is relatively low, so ArrayList is not suitable for scenarios where there are many insertions and deletions at any position . Moreover, after the expansion, it may bring about a waste of space . ArrayList is suitable for finding elements given the subscript position, and the time complexity can reach O(1). Therefore: LinkedList , the linked list structure, is introduced into the java collection.

Guess you like

Origin blog.csdn.net/crazy_xieyi/article/details/126883221