ArrayList源码解析(初始化、add()、remove())

ArrayList的源码并不难,ArrayList根本就是个数组,一个Object数组。Static变量会被jdk提前初始化好,且只有一份,独一无二。

与HashMap不同,Arraylist的初始化容量是10.

另外之所以New两个arraylist,两者均指向在堆中的Object数组。主要为了避免反复创建无用数组,造成性能上的浪费。

初始化过程:

通过默认构造方法初始化一个空Object数组。执行add方法时调用一个ensureCapacityInternal方法。

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

前面有一个int类型的成员变量,初始化默认为0,minCapacity传入为1.如果底层的Object数组就是缓存数组,取两个之中最大的最为参数继续。因为之前在构造方法里已经将elementData引用了DEFAULTxxxx这个Object数组。所以最开始是if判断是true的。然后Math.max比较默认容量和size的大小,最开始size<10,所以数组长度初始化为10,后来size++逐渐大于10,重新初始化数组长度。

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

如果传过来的值大于之前的数组,即需要扩容数组,则执行grow。

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

先获取老数组的长度,移位运算将其扩充为之前的1.5倍。Grow里面有一个Array.copyof方法,用了一个native修饰符修饰,底层是有一个System.arrayCopy方法,通过C/C++实现的数组拷贝,性能强大。比使用for循环复制数组强一些。

后面两条判断是为了保证新的数组在合理范围内。

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

回到最开始的add,elementData数组初始化完成之后,将元素填充进去。

这里写图片描述

最后,Arraylist和hashmap一样,都可以通过指定容量以提高其性能,免去多余的扩容过程。Arraylust也是通过构造方法指定初始化容量。

DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个是final的10.

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

ArrayList的remove方法的实现
Remove一个数组中的元素,它后面的元素会被复制,然后往前移动一个位置,使null只存在于数组末端,从而使遍历的时候不报空指针异常。

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; // clear to let GC do its work

        return oldValue;
    }

先是rangeCheck。然后记录修改次数,计算需要copy的元素位置,开始复制,然后将最后的位置置null,要注意的是,是复制后粘到下一位置,不是移动,然后原位置还是原元素,通过add用新元素将其覆盖掉即可。最后交给GC。但这种方法效率有点低,如果删除的是最后一个元素,不会触发数组底层的复制,时间复杂度为O(1)。如果删除第i的元素,会触发底层数组复制n-i次,根据最坏情况,时间复杂度为O(n)。这个问题在LinkedList有方案解决。

也是因此,ArrayList的存取效率很高(get/set)时间复杂度为O(n),而遍历、插入、删除效率并不高(remove/add/get)时间复杂度最差为O(n)。恰恰与Hashmap相反。

这种remove会返回你删除的value。除了这种通过下标删除的api,还有通过对象删除的一个remove重载函数。最后还有一个fastRemove,这种和通过下标remove没有任何不同,只是它没有返回值。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值