Exploring the source code of the super detailed ArrayList underlying implementation

If you want to save effort without looking at the analysis, you can go directly to the bottom to see the summary

JDK version description: 1.8

1. Storage method:

It is stored in an array and uses the transient keyword (deserialization) to save space . Because the defined array has a length, for example, the initial size of the ArrayList is 10, if only 5 elements are stored for the first time, and there is no transient keyword modification, the entire array will be serialized, not just the effective 5 elements, so ArrayList uses the transient keyword to implement the Serializable interface, and defines the writeObject() and readObject() methods to implement custom serialization.

 transient Object[] elementData; 

2. Construction method:

Three construction methods are implemented as follows:
1. Construction method with int: use the value passed in by the user as the initial array size

public ArrayList(int initialCapacity) {
    
    
       if (initialCapacity > 0) {
    
    
       	   //使用用户传入的值作为初始数组的大小
           this.elementData = new Object[initialCapacity];
       } else if (initialCapacity == 0) {
    
    //如果参数为0则返回一个空数组
           this.elementData = EMPTY_ELEMENTDATA;
       } else {
    
    
           throw new IllegalArgumentException("Illegal Capacity: "+
                                              initialCapacity);
       }
   }

2. No parameter construction: define the initial array size as 0;

public ArrayList() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

3. Construction method with collection: there will be a jdk bug, not to discuss here, the normal situation is to define the initial size of the array as 0

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. Common methods (mainly talk about ArrayList expansion mechanism)

add,addAll,remove,removeAll also has a weird retainAll, other simple ones will not be listed.

 3.1.add method:

The add method has two overloading methods:
1. There is only value, no index. This method will first re-judge the size of the capacity, and then decide whether to expand. If expansion is needed, expand first, and then store the value, and at the same time size index ++ ; If you don't need to expand, store the value directly, and size index ++ at the same time.
  To determine whether to expand and how to expand, please read the code below and the Chinese comments of the code. The English comments are the source code. If you have any questions, please leave a comment.

    /*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
	则相当于elementData[0] = e;size++;
	2.举例:第2次调用add方法,则相当于elementData[1] = e;size++; 
	3.举例:第11次调用add方法,elementData长度为10,先扩容,elementData=15,
	然后elementData[10] = e;size++; 
	*/
	public boolean add(E e) {
    
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
	//涉及的方法如下,主要是计算容器的大小是否能满足需要以及扩容

	private void ensureCapacityInternal(int minCapacity) {
    
    
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	/*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
	则会返回Math.max(DEFAULT_CAPACITY, minCapacity),其中DEFAULT_CAPACITY为10.
	2.举例:第2次调用add方法,elementData数组长度为10,返回minCapacity,值为2.
	3.举例:第11次调用add方法,elementData数组长度为10,返回minCapacity,值为11.
	*/
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    /*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
    则此时minCapacity =10,elementData.length=0,进入grow方法(ArrayList的第一次扩容)
      2.举例:第2次调用add方法,elementData数组长度为10.此时minCapacity = size+1 = 2,结束
      3.举例:第11次调用add方法,elementData数组长度为10,minCapacity=11
      所以minCapacity - elementData.length > 0,进入grow方法(ArrayList的第二次扩容)
    */
    private void ensureExplicitCapacity(int minCapacity) {
    
    
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    /*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
    oldCapacity =0,newCapacity =0,经过判断执行newCapacity = minCapacity,
    此时newCapacity=10 ,然后进入 copyOf方法后,得到的返回值应该长度为10的空数组,
    最后赋值给elementData ,完成第一次扩容.
      2.举例:第2次调用add方法时,未进入grow方法
      3.举例:第11次调用add方法,minCapacity =11,oldCapacity =10,newCapacity =15,
      因为oldCapacity + (oldCapacity >> 1) = 10 + 5(转换为二进制后右移一位,相当于java中,整数/2),
      newCapacity - minCapacity = 15-11 > 0,执行copyOf方法,作用如下:
      先创建一个长度为15的copy数组,然后把elementData中的内容全部赋值进copy数组中,
      再返回copy数组,最后重新赋值给elementData数组,完成第二次扩容.
    */
    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);
    }
    //加上数组的类型参数后进入重载的copyOf方法中
    public static <T> T[] copyOf(T[] original, int newLength) {
    
    
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    /*首先判断数组类型是否为Object类型,如果是true,
    则T[] copy =(T[]) new Object[newLength],即创建一个长度为10的数组,
    然后调用 System.arraycopy方法,这是jdk的本地方法,具体作用接着往下看,
    最后返回copy数组
    */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    
    
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
    /* 方法含义:将src数组从srcPos位置开始复制length长度的内容,
     * 粘贴到dest数组中,从destPos位置开始粘贴
     * @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied.
     * */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

2. There are values ​​and indexes

	public void add(int index, E element) {
    
    
		//判断索引是否合法,注意:index=size的情况是被允许的,因为刚好可以衔接上原数组
        rangeCheckForAdd(index);
		//进行容量判断
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /*已经经过容量判断,如需扩容会先扩容,因此不会产生越界问题,
        将从index开始长度为size-index的内容复制到起始位置为index+1的后面。
        */
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        //将要插入的元素插入进index位置
        elementData[index] = element;
        size++;
    }
    
	//涉及方法如下,已经展示过的方法将不再展示:

	//查看索引是否在正常范围
	private void rangeCheckForAdd(int index) {
    
    
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
 3.2addAll method

Role: add all elements in a collection to the current ArrayList

	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;
    }
    
    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;
        //复制元素,判断是验证index是否刚好等于size
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    
 3.3.remove method:

There are also two remove methods, the source code is relatively simple, no more description:
1. remove with index

public E remove(int index) {
    
    
		//判断索引是否合法
        rangeCheck(index);
        modCount++;
        //取出要删除的元素
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	//作用相当于把index后面的所有元素前移一位
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        //将最后一个元素置为null让GC来处理
        elementData[--size] = null; // clear to let GC do its work
		//返回要删除的元素
        return oldValue;
    }

2. Remove with element content

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比remove(int index)方法仅少了验证index和取出并返回元素内容的部分
	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
    }
3.4 removeAll method:

Function: Delete the intersection of the current set and the elements contained in the set parameters. Return true if there is one.

 	public boolean removeAll(Collection<?> c) {
    
    
 		//判断是否为null
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
	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++)
            	//将不符合删除条件的放入elementData中
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
    
    
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
    
    
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
    
    
            	//将符合删除条件的元素置为null,等待GC删除
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                //将size设为不符合删除条件元素的总和
                size = w;
                modified = true;
            }
        }
        return modified;
    }
3.4 retainAll method:

Function: Take the intersection of the original set and the parameter set. If there is, the original set is the intersection, if not, the original set is set to empty. (This method is really fierce)

	public boolean retainAll(Collection<?> c) {
    
    
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }
    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++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
    
    
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            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;
    }

to sum up

Finally, let’s summarize the core expansion mechanism of AraryList, using the parameterless construction method as an example
. 1. When the construction method is called, the lazy loading method is adopted, that is, the initial size of the instance of AraryList is 0, and it will not be until the first add. Will change the size to 10, this is the first expansion, from 0 to 10.
2. After the first expansion, from the second add to the tenth time, there will be no expansion operations until the 11th add, that is, after the last time an element is inserted, the array is full, then the next time it will be triggered Expansion mechanism. The capacity of the new array is newCapacity = oldCapacity + (oldCapacity >> 1), which may be exactly 1.5 times the size of the original array, or about 1.5 times, depending on whether the size of the original array is even. This is the second expansion.
3. Each subsequent expansion conditions and expansion mechanism are the same as the second time, which is still very simple compared to HashMap.
Finally, there are too many branches in some places, and the text is very complicated to explain. If friends have questions about any place, please write in the comment area. I will reply in detail as soon as possible after reading it.

Guess you like

Origin blog.csdn.net/TreeCode/article/details/107961956