ArrayList源码分析

ArrayList

概述

ArrayList底层通过数组的方式来实现List接口,size、isEmpty、get、set等操作都是O(1)时间复杂度,而add是均摊常数时间复杂度(amortized constant time,可以理解为扩容的频率不太高,主要关注统计情况下的复杂度,因此均摊到所有操作上就是常数时间)。
size表示元素的数目,capacity表示当前数组的容量,ArrayList在添加元素时会进行判断,若数目超过了容量会进行自动扩容。
该容器不是线程安全的,可通过Collections.synchronizedList()方法来转化为线程安全容器,这应该在创建的时候完成。
iterator返回的迭代器是fail-fast的,就是说如果返回迭代器后,用除了ListIterator的remove和add的其他方法进行修改时,会抛出ConcurrentModificationException,这主要是为了避免一些随机的风险。

源码分析

  1. 构造器
    主要的两个构造器如下:
// 创建一个默认容量的空列表
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_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);
        }
    }

逻辑很简单,没有参数时是构造一个空数组,传入一个capacity时也只是判断一下数值得范围做检查而已。
EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个static final的空数组,其实就是用作一个共享的状态,既然都是空数组,为什么有两个?区别在于add第一个元素时的扩容机制不同,后面分析add时会看到。

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

// 保证内部数组的容量能够容纳元素,必要时进行扩容
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//        DEFAULT_CAPACITY为10,所以如果构造时没传容量,是默认的空列表,第一次添加元素时minCapacity为max(10,1)=10;如果是容量为0的空列表,这句不会执行。
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }


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

主要的逻辑都注释了,grow方法就是扩容的代码,主要就是扩充为1.5倍,如果没有溢出就用Arrays.copyOf复制到新的数组,可见这种方式复制数组是比较快的。
3. remove

// 删除索引上的元素。删除元素后,index后面的元素都会向前移动
    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);
        // 注意到这一行,手动设为null是为了防止内存泄漏
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    // 删除数组中第一个对应的元素
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                // fastRemove的逻辑其实与上一个remove中的逻辑基本一样,移动后面元素,只是少了边界检查。
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

整体逻辑也比较简单,检查了范围后用System.arraycopy来移动元素,并将最后一个元素设为null防止内存泄漏。
另外也可以看到,删除元素时是不会缩小数组的容量的,如果要节省空间,可以使用trimToSize来收缩数组.
4. get和set

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

这两个方法就很简单了,检查一下范围就直接进行数组的对应操作。

总结

总的来说ArrayList比较简单,主要要知道底层是由数组实现的列表,非线程安全,了解其扩容机制,使用时就心中有数了。
ArrayList随机查找是常数时间,而随机增删元素时是线性时间,适合查找较多、修改较少的顺序存储的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值