ArrayList源码分析

来源:互联网 发布:vb里头mid函数查找 编辑:程序博客网 时间:2024/06/01 09:15

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随机查找是常数时间,而随机增删元素时是线性时间,适合查找较多、修改较少的顺序存储的场景。

原创粉丝点击