超级详细的ArrayList底层实现之探究源码

想省力不看分析,可以直接去最下面看总结

JDK版本说明:1.8

1.存储方式:

采用数组的方式存储,并且使用了transient关键字(反序列化),目的是节省空间。因为定义数组是有长度的,比如ArrayList初始大小是10,若第一次只存了5个元素,且没有transient关键字修饰的话,会序列化整个数组,而不仅只是有效的5个元素,所以ArrayList使用transient关键字,实现了Serializable接口,并且定义了writeObject()和readObject()方法,实现自定义序列化。

 transient Object[] elementData; 

2.构造方法:

三种构造方法,实现如下
1.带int的构造方法:使用用户传入的值作为初始数组的大小

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.无参构造:定义初始数组大小为0;

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

3.带集合的构造方法:这里面会有一个jdk的bug,这里不做讨论,正常情况也是定义数组的初始大小为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.常见方法(主要讲ArrayList扩容机制)

add,addAll,remove,removeAll还有一个奇葩的retainAll,其他比较简单的就不列出来了。

 3.1.add方法:

add方法有两种重载方式:
1.只有值,没有索引,这个方法首先会重新判断容量的大小,然后决定是否扩容,如果需要扩容则先扩容,然后将值存入,同时size索引++;如果不需要扩容则直接将值存入,同时size索引++。
  具体判断是否扩容以及如何扩容,请阅读下面代码及代码中文注释,英文注释是源码,仍有问题请评论留言。

    /*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.有值,有索引

	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方法

作用:将某个集合中的全部元素加入当前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方法:

remove方法同样有两种,源码比较简单,不再过多叙述:
1.带索引的remove

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

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方法:

作用:删掉当前集合与集合参数中所含元素的交集。有一个就返回true。

 	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方法:

作用:原集合与参数集合取交集,如果有,原集合为交集,如果没有,则原集合设为空。(这方法真猛)

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

总结

最后总结一下最核心的AraryList扩容机制吧,用无参构造方法举例
1.调用构造方法创建时,采用懒加载的方式,即AraryList的实例的初始size是0,等进行第一次add时,才会把size变成10,这是第一次扩容,从0到10。
2.第一次扩容之后,从第二次add到第十次,都不会有扩容操作,直到第11次add,即上一次插入元素后,数组已经存满了,那么下一次就会触发扩容机制。新数组的容量是newCapacity = oldCapacity + (oldCapacity >> 1),可能正好是原数组大小的1.5倍,或1.5倍左右,关键看原数组大小是不是偶数,这就是第二次扩容。
3.之后的每次扩容条件以及扩容机制就都和第二次一样了,相对HashMap还是很简单的。
最后,有些地方实在是分支太多,文字解释起来十分复杂,如果朋友们对哪个地方有所疑问,还请在评论区写下,我查阅后会尽快详细回复。

猜你喜欢

转载自blog.csdn.net/TreeCode/article/details/107961956