Java ArrayList&Vector 源代码分析

ArrayList 对象继承了AbstractList对象,这就是说,ArrayList可以使用迭代器来操作,但是有一点要注意,上一次我们看AbstractList源代码时,知道这个迭代器是快速失败的,也就是说他记录了修改的次数,因此在实现这个ArrayList的添加操作时,我们也需要随时更新这个操作记录。也就是如下这个变量。

protected transient int modCount = 0;

现在从头开始看ArrayList对象的使用。
第一步是构造函数,我们经常使用如下语句来初始化对象。

List<Object> list = new ArrayList<Object>();

实际上调用的构造函数如下:

public ArrayList() {
        this(10);
    }

这里使用了另外一个构造函数,

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

这个构造函数就是初始化了一个Object数组。并在初始化之前检查了参数范围。我们平时使用的时候,默认都是10.

现在看一下最常用的添加元素操作,源代码如下:

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

这里添加逻辑很简单,就是在数组末尾添加了这个元素。但是添加之前,有一个ensureCapacityInternal操作,这个操作就很重要了。我们看看做什么的?

private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

这里首先给父类的modCount变量加1,就是说这个容器又多了一个操作,这个主要用于检查多线程环境来使用的。

另外,这里JDK代码中注释了一句overflow-conscious code,检查了,如果数组末尾的这个下标已经超出了数组长度的话,那么就需要扩展数组的长度了。这里使用了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);
    }

这个方法逻辑就是扩展数组的长度,长度扩展逻辑是旧的容量除2,再加上以前的容量,换句话说就是一半一半增加的。这里又判断,如果新的容量还是不够的话,那么就直接将最新的数组下标值赋值给数组长度,再进一步检查,新的容量是否比Integer.MAX_VALUE-8还要大,这里为什么是这个数值,JDK的源码中注释了如下说明:

/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

是说有一些JVM会保存一些头字段存放在数组中。如果清理比这个更大的数组,就会导致内存溢出。所以这里是JVM的限制了。
所以整个添加操作到这里基本就结束了。

看一下删除操作:

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; // Let gc do its work

        return oldValue;
    }

这里简单说明下删除的逻辑:

  1. 判断数组下标的范围是否超过数组容量。
  2. 增加操作的记录。
  3. 检查删除的元素是否在数组末尾,如果不是,则将index后的元素迁移。
  4. 最后一步很重要,就是将删除元素所留下来的位置,置为null,方便GC工作。

这里简单说一句,将变量置为null,我以前一直以为是纯属装B的工作,变量过了作用域自然会被GC清理,但是这里却也这么做了,究其原因,是因为这是数组,不是一个简单的变量。

说明一下Vector对象的实现,这个对象也是实现了AbstractList接口,实现和ArrayList一样,但是有一点不同,就是Vector对象中所有的操作接口都是用了synchronized关键字,也就是说每个操作都给Vector对象实例加锁了。这个和StringBuilder&StringBuffer的差异是一样的。

你可能感兴趣的:(Java源代码分析)