Java Collections Series 1, ArrayList

1. ArrayList overview

The underlying data structure of ArrayList is a dynamic array , so we can call it an array queue. ArrayList dependencies:

public class ArrayList<E> extends AbstractList<E>
    	implements List<E>, RandomAccess, Cloneable, java.io.Serializable

As can be seen from the dependencies, ArrayList is firstly a list, and secondly, he has the related functions of the list, which supports fast (fixed time) positioning of resources. Copy operations can be performed and serialization is supported. Here we need to focus on AbstractLit and RandomAccess. This class, one is to define the basic properties of the list, as well as determine the general actions in our list. RandomAccess mainly provides the function of quickly locating the resource location.

2. ArrayList member variable

  /**
     * Default initial capacity.数组默认大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     空队列
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
        如果使用默认构造方法,则默认对象内容是该值
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
        用于存储数据
     */
    transient Object[] elementData; 

     // 当前队列有效数据长度
      private int size;

     // 数组最大值
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

In the source code of ArrayList, there are mainly the above member variables:

  • elementData : dynamic array, which is the core array where we store data
  • DEFAULT_CAPACITY: The default length of the array, which will be introduced when the default constructor is called
  • size: record the effective data length, the size() method returns this value directly
  • MAX_ARRAY_SIZE: The maximum length of the array, if the expansion exceeds this value, set the length to Integer.MAX_VALUE

Extended thinking: EMPTY_ELEMENTDATA and DEFAULTCAPACITY_EMPTY_ELEMENTDATA are two empty array objects. What is the difference between them? We will do a detailed comparison when we explain the construction method in the next section.

3. Construction method

Three constructors are provided in ArrayList :

  • ArrayList()
  • ArrayList(int initialCapacity)
  • ArrayList(Collection c)

Depending on the constructor, the construction method will be different. In our usual development, ArrayList (int capacity) may be called inside the default constructor, but the internal implementation of different constructors in ArrayList is different , mainly related to the member variables mentioned above .

3.1 ArrayList()

It is described in the comments given in the source code as follows: Construct an empty list with an initial capacity of ten

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

As you can see from the source code, it just points elementData to the storage address of DEFAULTCAPACITY_EMPTY_ELEMENTDATA, and DEFAULTCAPACITY_EMPTY_ELEMENTDATA is actually an empty array object, so why does it say that it creates a list with a default size of 10?

Or let's think about it from another angle, what if we need to add elements to this empty array?

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //确认内部容量
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        // 如果elementData 指向的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            设置默认大小 为DEFAULT_CAPACITY
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //确定实际容量
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果超出了容量,进行扩展
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    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);
    }
    

The above code block is relatively long, here is a brief summary:

1. add (E e): To add an element, first determine the length of the elementData array, and then set the value

2. ensureCapacityInternal (int minCapacity): determine whether element is empty, if so, set the default array length

3. ensureExplicitCapacity (int minCapacity): Determine whether the length of the expected growth array exceeds the current capacity, and if so, call grow()

4. grow(int minCapacity): expand the array

回到刚才的问题:为什么说创建一个默认大小为10 的列表呢?或许你已经找到答案了~

3.2 ArrayList(int initialCapacity)

Initializes the array size in ArrayList according to the specified size. If the default value is greater than 0, initialize according to the parameter. If it is equal to 0, it points to the memory address of EMPTY_ELEMENTDATA (similar to the usage of the default constructor above). If less than 0, an IllegalArgumentException is thrown.

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

Extended thinking: Why is EMPTY_ELEMENTDATA used here instead of DEFAULTCAPACITY_EMPTY_ELEMENTDATA like the default constructor ? Interested children's shoes can think for themselves, and the knowledge after thinking is yours~

3.3 ArrayList(Collection c)

Convert the data stored in Collection<T> c into an array form (toArray() method), and then judge whether the current array length is 0. If it is 0, only the default array (EMPTY_ELEMENTDATA) is required; otherwise, the data is copied.

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

3.4 Summary

As can be seen from the above three construction methods, in fact, what each constructor does is different, especially the direct difference between the default constructor and the two constructors ArrayList(int initialCapacity), we need to make some differences .

  • ArrayList(): Points to DEFAULTCAPACITY_EMPTY_ELEMENTDATA . When the list is used, it will be initialized. The default size of the array will be set by judging whether it is the DEFAULTCAPACITY_EMPTY_ELEMENTDATA object.
  • ArrayList(int initialCapacity): When initialCapacity > 0, set the length. If initialCapacity = 0, it will point to EMPTY_ELEMENTDATA and will not set the default array length when using it.

Therefore, the essential difference between DEFAULTCAPACITY_EMPTY_ELEMENTDATA and EMPTY_ELEMENTDATA is whether or not the default array length will be set.

4. Add method (Add)

ArrayList adds four addition methods:

  • add(E element)
  • add(int i , E element)
  • addAll(Collection<? extends E> c)
  • addAll(int index, Collection<? extends E> c)

4.1 add(E element)

First look at the source code of add(T t):

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 元素个数加一,并且确认数组长度是否足够 
        elementData[size++] = e;		//在列表最后一个元素后添加数据。
        return true;
    }

Combined with the default constructor or other constructors, if the default array is empty, the array will be initialized when the ensureCapacityInternal() method is called. That's why when the default constructor is called, we create an empty array, but it is described as an array of length 10 in the comments.

4.2 add(int i , T t)

   public void add(int index, E element) {
    // 判断index 是否有效
        rangeCheckForAdd(index);
    // 计数+1,并确认当前数组长度是否足够
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); //将index 后面的数据都往后移一位
        elementData[index] = element; //设置目标数据
        size++;
    }

This method is actually similar to the above add. This method can insert elements according to the position of the element and the specified position. The specific execution logic is as follows:

1) Make sure that the position where the number is inserted is less than or equal to the current array length, and not less than 0, otherwise an exception is thrown

2) Make sure that the array has used the length (size) plus 1 to store the next data

3) The number of modifications (modCount) is automatically incremented by 1. If the current array length (size) plus 1 is greater than the current array length, the grow method is called to grow the array

4) The grow method will change the length of the current array to 1.5 times the original capacity.

5) After ensuring that there is enough capacity, use System.arraycopy to move all the elements after the position (index) that needs to be inserted back one place.

6) Store the new data content in the specified position (index) of the array

4.3 addAll(Collection<? extends E> c)

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

The addAll() method completes the addition of the entire collection data by converting the data in the collection into Array[] and adding it to the elementData array. At the beginning of nothing special on the whole, the collection here may throw a control exception NullPointerException, which needs attention.

4.4 addAll(int index,Collection<? extends E> c)

 public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

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

Compared with the above method, there are mainly two more steps here, to determine whether the position of adding data is at the end, and if it is in the middle, you need to move the data backward by the length of the collection.

5. Remove method (Remove)

There are five ways to delete data in ArrayList:

  • remove(int i)
  • remove(E element)
  • removeRange(int start,int end)
  • clear()
  • removeAll(Collection c)

5.1、remove(int i):

Deleting the data does not change the length of the array, it only removes the data rearrangement. If the target has no other valid references, it will be recycled during GC.

public E remove(int index) {
        rangeCheck(index); // 判断索引是否有效
        modCount++;
        E oldValue = elementData(index);  // 获取对应数据
        int numMoved = size - index - 1;  // 判断删除数据位置
        if (numMoved > 0) //如果删除数据不是最后一位,则需要移动数组
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // 让指针最后指向空,进行垃圾回收
        return oldValue;
    }

5.2、remove(E element):

In this way, the array will be traversed in the AccessRandom method internally. When the matched data is equal to the Object, fastRemove() will be called to delete it.

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    

fastRemove( ): The fastRemove operation is consistent with the above-mentioned deletion based on subscripts.

   private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

5.3、removeRange(int fromIndex, int toIndex)

This method mainly deletes the data in the range, and can overwrite the whole part of the data through System.arraycopy.

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

5.4、clear()

Directly set the entire array to null, which will not be described in detail here.

5.5、removeAll(Collection c)

Mainly by calling:

    private boolean batchRemove(Collection<?> c, boolean complement) {
        //获取数组指针
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                //根据 complement 进行判断删除或留下
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 进行数据整理
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

There are also calls in retainAll (Collection c), the main functions are to delete the elements contained in this collection and leave the elements contained in this collection.

expand thinking

After knowing the deletion method of ArrayList, and then combining our commonly used deletion methods, we will think about which steps will cause problems. We usually choose the variable list, and if it matches, delete it. We traverse in the following ways:

  • foreach(): ConcurrentModificationException mainly occurs
  • for(int i;**;i++): The same data is skipped, please refer to: https://blog.csdn.net/sun_flower77/article/details/78008491
  • Iterator traversal: ConcurrentModificationException mainly occurs, please refer to: https://www.cnblogs.com/dolphin0520/p/3933551.html

An effective way to avoid ConcurrentModificationException is to use CopyOnWriteArrayList under the Concurrent package, which will be analyzed in detail later

6、toArray()

ArrayList provides 2 toArray() functions:

  • Object[] toArray()
  • T[] toArray(T[] contents)

Calling toArray() will throw a "java.lang.ClassCastException" exception, but calling toArray(T[] contents) returns T[] normally.

toArray() throws an exception because toArray() returns an Object[] array, and converting Object[] to other types (eg, converting Object[] to Integer[]) will throw "java. lang.ClassCastException" exception because Java does not support downcasting.

toArray() source code:

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    

7、subList()

If we need to obtain a certain part of the data in the collection for operation during the development process, we can obtain it by using the SubList() method, which will create an inner class SubList() of ArrayList.

SubList inherits AbstractList and implements most of the AbstractList actions.

It should be noted that a certain part of the data in the collection returned by SubList will be associated with the original collection. That is, when we operate on the Sublist, it will still affect the original collection. Let's take a look at the add method in Sublist:

  	public void add(int index, E e) {
        rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

It can be seen that the addition operation in the Sublist actually calls the addition operation in the parent (that is, the original set). Therefore, when using the subList method, you must think clearly whether you need to modify the elements of the subcollection without affecting the original list collection.

Summarize

ArrayList is relatively simple in general, but ArrayList has the following characteristics:

  • ArrayList implements its own serialization and deserialization methods because it implements private void writeObject(java.io.ObjectOutputStream s) and private void readObject(java.io.ObjectInputStream s) methods by itself

  • ArrayList is implemented based on the array method, with no capacity limit (it will expand)

  • When adding elements, you may need to expand the capacity (so it is best to pre-judgment), when deleting elements, the capacity will not be reduced (if you want to reduce the capacity, trimToSize()), when deleting elements, set the deleted position element to null, the next gc The memory space occupied by these elements will be reclaimed.

  • thread unsafe

  • add(int index, E element): When adding an element to the specified position in the array, it is necessary to copy the position and all the elements behind it one bit backwards.

  • get(int index): When getting the element at the specified position, you can get it directly by the index (O(1))

  • remove(Object o) needs to traverse the array

  • remove(int index) does not need to traverse the array, just judge whether the index meets the conditions, the efficiency is higher than remove(Object o)

  • contains(E) needs to traverse the array

  • Traversing with iterator may throw multithreading exception

expand thinking

  • Extended thinking 1. How does the RandomAccess interface achieve rapid resource location?
  • Extended thinking 2. What is the role of EMPTY_ELEMENTDATA and DEFAULTCAPACITY_EMPTY_ELEMENTDATA?
  • Expanding thinking 3. The pit of the remove method?
  • Extended thinking 4: Why is ArrayList not thread-safe?

References

http://www.cnblogs.com/skywang12345/p/3308556.html https://blog.csdn.net/daye5465/article/details/77971530 https://blog.csdn.net/daye5465/article/details/77971530

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325388239&siteId=291194637