底层角度看ArrayList究竟有多'能'

首先可以先看看arrayList的继承实现结构,arrayList继承了AbstractList类 并实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。先来看看它的继承和实现结构吧
底层角度看ArrayList究竟有多'能'_第1张图片

  ArrayList有三个构造函数,分别是:

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);
    }
}
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

我们可以看出在arrayList的空参构造中,是直接将
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
空数组赋给了arrayList的元素数组,这里可能会有个疑问,那这样构造的话,初始化的数组长度在哪声明了呢?

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
 

transient Object[] elementData; // non-private to simplify nested class 

可以看到,在官网的解释上,提到了在使用带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空arrayList的时候,它会在第一次添加元素的时候为它扩充容量,扩充多少呢?
/**

  • Default initial capacity.
    */
private static final int DEFAULT_CAPACITY = 10;

就是它的默认初始化容量值。
在另外的一个带初始化容量值的构造函数中

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

无非就是根据你输入的容量值来为你生成一个object数组。但是你会发现它在容量值为0时,为它赋了另一个数组
/**

  • Shared empty array instance used for empty instances.
    */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    就是它,在jdk1.8中,arrayList新添加了这么一个空数组,这时候我也纳闷为什么需要两个空数组。。在官方给的解释中看到说是为了和EMPTY_ELEMENTDATA区分开来,更好的了解到在第一次添加元素时arrayList的扩容量。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

还有最后一个构造,带集合的构造:

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

这个构造也时将传入的集和转成数组,并对数组的类型做校验,接着将数组的内容复制到一个新数组中并赋值给elementData;
看完了构造,接着看看arrayList中常用的方法吧,用的最多也就是添加,获取值和移除了吧

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return 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;
    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);
}

比较繁琐的也就是add方法了,因为需要对数组做一个容量判断,判断需不需要进行数组扩容。
我们可以看到在调add方法中 在计算容量的方法中对数组做了个判断,判断是否为空数组,为空数组则进行第一次添加,容量为默认的初始化值为10,若是对不为空的数组做add时,则会做一个容量是否已满的判断

  if (minCapacity - elementData.length > 0)
        grow(minCapacity);

若已满,则会进行扩容,可以看到扩容的值也就是之前数组长度右移一位再加上数组长度,也就是之前数组的1.5倍,若是容量还是不够,则直接扩容到size+1的值,若容量值比ArrayList的默认最大长度还大的话,直接扩容到integer类型的最大值也就是2的31次方-1,最后将数组复制并赋值给elementData.

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

添加方法中还有一个add方法的重载,原理就是为新进来的同志让位置!将要新添加进来的元素后面的值都往后移动一个单位,这也是数组增删慢的原因,老同志搬位置忒慢了呀。
在添加元素是还会对下标进行判断,大于数组长度和小于0都会报下标异常。同样的方式,它还会去判断数组的容量是否足够,并进行扩容。这里的数组复制用的是System类中的一个Native方法,

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

src表示源数组,srcPos表示源数组中要复制的起始位置,desc表示目标数组,length表示要复制的长度。

增加看完了看看如何删除吧:

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

删除下标对应元素的步骤也就是先将要删的元素取出,并让那些在要走的同志后面的老同志都往前移一位,最后做数组赋值,并将数组末位元素置为空,注释也说了gc会回收它的。最后将删除的值返回。
还有另一个直接移除元素的方法:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
private void fastRemove(int index) {
    modCount++;
    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
}

这个方法可以看出,arrayList还对空值做了单独的判断,因为arrayList是可以存在Null值的,
由于equals是值比较,这里就对Null值单独做了判断,判断不一样,但步骤是一样的,去遍历这个数组,找到与要移除的元素相等的后调用fastRemove,里面所做的事也是让那些在移除元素后面的元素向前移动一位。

arrayList获取值的方法就不用多说了,很简单,对传入下标进行下标值是否越界,无则将下标所对应元素返回,这里rangeCheck没有对负值做校验是因为下标为负值的话系统也会抛出异常的

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

    return elementData(index);
}
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

在arrayList中用的比较多的方法就这些了,但是在arrayList中还有一些值得去阅读的点,我们可以看到在arrayList的存数据的数组是被transient修饰的,这个修饰符就表示这个字段是不参与序列化的过程的,而arrayList是实现序列化接口了,如果这字段不参与序列化的话,那数据岂不是丢失了?
transient Object[] elementData; // non-private to simplify nested class access
但如果你看完了整个ArrayList的源码,你会发现,它这个数组的序列化和反序列化过程是通过写进流和读取流中数据来实现的。可以看下以下的方法:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

arrayList通过调用writeObject这个方法将类里的非静态字段以及size(数组长度)写入了流中,而数据实在for里通过遍历数组长度,将值一一写入
for (int i=0; i s.writeObject(elementData[i]);
}

为什么arrayList要通过这种方式进行序列化呢,我的理解是由于arrayList有动态扩容的机制,这样的情况下会出现扩容后,数据未满的情况,这样数组中就有许多空值,如果直接进行类序列化的话,会造成一些资源浪费和序列化时间上的浪费,所以通过这种一个一个写入流的方式,节省时间和空间。

看完了序列化,再看看反序列化是不是就简单多了

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

在反序列过程中,先将存放数据的数组置为空数组,紧接着进行从流中读取数据的操作,同样的去计算数组的容量,看看是否需要进行扩容。这就是arrayList的序列化和反序列过程。

读到这仿佛有一种拨开云雾的感觉,接着又来一片乌云,因为看到了这么一个字段
modCount
What?,这是干嘛用的,怎么每次都看见它,莫非有很大用处。
通过不断的追溯,发现这个字段是它父亲(AbstractList)的
protected transient int modCount = 0;
我们可以在之前的代码中看到,在arrayList进行结构上的修改时(add,remove还有扩容操作等等),会发现都会进行modCount++,那这作用是什么呢,在哪使用到了它,别急,接着往下看。
在arrayList里有这么一个内部类实现

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

在这个内部类中,定义了三个整形变量,分别是cursor(下一个元素的索引位置),lastRet(上一个元素的索引位置)以及expectedModeCount(预期修改的次数)并将modCount的值赋与它,
在之后进行迭代,移除等操作时都会去校验modCount和expectedModeCount的值是否相等,这样的目的就是判断在迭代过程中是否有其他修改arrayList结构的操作在同时进行着,若并发操作着的modCount值和expectedModCount值不等,则会报一个并发修改的异常,用这种校验机制也是防止出现数组下标越界这些异常。

你可能感兴趣的:(java)