Java ArrayList源码深入分析

来自:网络
时间:2022-12-26
阅读:

1.ArrayList 是基数组结构的,需要连续的内存空间

从构造函数可以看出,ArrayList内部用一个Object数组来保存数据。

对于无参构造,ArrayList会创建一个大小为10的初始数组,第一次扩容时会创建默认大小数组。

 //无参构造函数,会构造一个空数组。第一次扩容时会创默认大小为10的数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //如果给定集合大小,会创建一个对应大小的数组
    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);
        }
    }

2.对于set和get方法ArrayList效率高,因为可以直接通过下标获取存储对象。

 //直接通过角标从数组中取出元素
    public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        return (E) elementData[index];
    }
    //set 方法,直接通过index 拿到元素,进行修改就行。
    public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }

3.对于add方法,有两种情况:

第一种是在尾部添加,不需要移动数组。

//Add 方法 ,在末端插入不需要
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

第二种情况在指定位置添加或插入数据,则需要移动数组,效率比较低。如果插入在第0个位置则需要移动整个数组。

  public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //被插入的元素在index。则移动的数组应该从index开始,向后移动一个,
		//移动的数组长度 size-index 从index开始的数组长度
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        elementData[index] = element;
        size++;
    }

在add时还需要考虑到数组的扩容问题,看ensureCapacityInternal方法

 private void ensureCapacityInternal(int minCapacity) {
        //通过无参构造创建为设置初始大小时,判断条件为true
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //创建默认大小的数组,初始大小是10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

添加元素后,元素数量大于数组长度时,进行扩容grow,扩容大小是原数组的1.5倍

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        //新增后,数组容量已经大于了数组长度,则进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
		//新的数组大小是原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);  
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //创建一个新长度的数组。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

4.对应remove 删除元素操作。如果删除元素在最后一个,不需要移动数组,否则会将被删除元素之后的所有元素都要向前移动,效率比较低。

 //删除
    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        modCount++;
        //取出被删除的元素
        E oldValue = (E) elementData[index];
        int numMoved = size - index - 1;
		//移动的数组长度,大于0才需要移动,小于0说明被删元素在最后一个
        if (numMoved > 0)
            //第一个参数是源数组,
            //第二个参数是移动的起始位置:index+1,从被删元素的后一个位置开始
            //第三个参数是目标数组
            //第四个参数是移动目标位置:index,被删除元素的位置
            //第五个参数是移动的数组长度 size - index - 1,从index+1位置开始之后的数组长度
            //最终会调用到native方法进行移动。
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

5.System.arraycopy方法参数分析。

Java ArrayList源码深入分析

第二个参数 index+1是被移动数组的起始位置,即被删元素后一个位置

第四个参数index,是被移动数组的起始位置,即被删元素的位置。

最后一个参数size-index-1,被移动数组的长度,即被删元素后一个位置到元素末尾的长度

public static native void arraycopy(java.lang.Object src, int srcPos, java.lang.Object dest, int destPos, int length);

通过对源码的阅读,也许会对ArrayList有一个更加直接的了解。

返回顶部
顶部