Java ArrayList

412 阅读4分钟

ArrayList我用到很多今天来记录一下。

1:结构

ArrayList底层实现是一个数组。如下图

java8 部分源码:

protected transient int modCount = 0;//当前数组结构变动(增删)的次数
transient Object[] elementData;//保存数据的数组
private static final int DEFAULT_CAPACITY = 10;//数组初始大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA =
        new Object[0];
private int size;//数组大小
//数组最大容量。2147483647 (Integer.MAX_VALUE)
private static final int MAX_ARRAY_SIZE = 2147483639;
//共有三个构造方法,为减少长度只列出了一个
//无参数直接初始化,数组大小为0.
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

我看源码的顺序: 先熟悉API,之后阅读类的注释看类结构(主要看方法名,出名的源码一般都很达意)做到心里有一个轮廓不至于一头雾水。

类注释

1:可存储所有元素包括null(Implements all optional list operations, and permits all elements, including null.)。
2:size、isEmpty、get、set、iterator和listIterator是O(1)
  add和remove是O(n)
3:自动扩容
4:非线程安全.要想安全可以使用

List list = Collections.synchronizedList(new ArrayList(...));

5:迭代器是快速故障的(除了通过迭代器自己的remove或add方法)。创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法,迭代器将抛出ConcurrentModificationException。

方法

ArrayList中我们常用的add(),remove()以及扩容底层实现逻辑是什么呢?

add()

1:先判断是否需要扩容,需要扩容则进行扩容的一系列操作
2:直接赋值,全程没有加锁,线程不安全。

线程不安全的原因:共享可变的资源,拆开来讲就是需要满足两个条件 共享的和可变的。

源码如下:

//Java 8 
public boolean add(E var1) {
    //1,首先判断是否需要扩容数组
    this.ensureCapacityInternal(this.size + 1);
    //2,直接赋值
    this.elementData[this.size++] = var1;
    return true;
}

扩容

1: 扩容的规则是 扩容后的容量 = 原来的容量 + 原来的容量/2
2: 扩容底层是调用System的arraycopy方法,此方法是native的。它会新建一个我们扩容后容量大小的新数组,然后将原数组的数据遍历一一对应拷贝过去。

因为数组拷贝是比较耗资源的,我们使用ArrayList的时候,新增数据时尽量使用addAll方法。创建时最好初始化合适的大小,防止频繁扩容。

源码如下:

private void ensureCapacityInternal(int var1) {
    this.ensureExplicitCapacity(
        calculateCapacity(this.elementData, var1));
}
private static int calculateCapacity(Object[] var0, int var1) {
    //(无参初始化的ArrayList第一次扩容最少10容量)
    return var0 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(10, var1) : var1;
}
private void ensureExplicitCapacity(int var1) {
    //数组结构变动 +1。
    ++this.modCount;
    //数组大小若是大于数组容量则扩容,小于则返回
    if (var1 - this.elementData.length > 0) {
        this.grow(var1);
    }
}
//扩容,并把现有数据拷贝到新的数组里面去
private void grow(int var1) {
    int var2 = this.elementData.length;
    // >> 1 是把 var2 除以 2 的意思,即扩容1.5(1+0.5)倍
    int var3 = var2 + (var2 >> 1);
    if (var3 - var1 < 0) {
    //若将原数组容量扩大1.5倍还不满足,则接将数组容量改为所需大小
        var3 = var1;
    }
    if (var3 - 2147483639 > 0) {
        var3 = hugeCapacity(var1);
    }
    this.elementData = Arrays.copyOf(this.elementData, var3);
}
private static int hugeCapacity(int var0) {
    if (var0 < 0) {//(Integer.MAX_VALUE + 1)为 -2147483648
        throw new OutOfMemoryError();
    } else {//数组最大容量 Integer.MAX_VALUE
        return var0 > 2147483639 ? 2147483647 : 2147483639;
    }
}

//System.class
public static native void arraycopy(Object var0, int var1, 
    Object var2, int var3, int var4);

remove()

1: 因为我们新增的时候允许重复和为null,因此remove时候他只删除第一个出现的符合条件的值。
2: 删除是使用的equals方法进行比较,因此我们需要关注equals方法的实现。
3:当remove某一个元素后,ArrayList为了保持数组结构会将它后边的元素往前移动。add(int index,E e)是将 等于大于index的元素向后移。

我们使用ArrayList要删除多个数据时,最好removeAll减少数组拷贝次数。

相关源码:

public boolean remove(Object var1) {
    int var2;
    if (var1 == null) {//删除第一个null
        for(var2 = 0; var2 < this.size; ++var2) {
            if (this.elementData[var2] == null) {
                this.fastRemove(var2);
                return true;
            }
        }
    } else {//删除第一个出现的符合条件的值。
        for(var2 = 0; var2 < this.size; ++var2) {
            if (var1.equals(this.elementData[var2])) {
                this.fastRemove(var2);
                return true;
            }
        }
    }
    return false;
}
private void fastRemove(int var1) {
    ++this.modCount;//数组结构变动 +1
    int var2 = this.size - var1 - 1;
    if (var2 > 0) {
        //拷贝
        System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);
    }
    this.elementData[--this.size] = null;
}

迭代器

迭代器的部分源码:

int cursor;//游标,下个元素的位置
int lastRet = -1;
int expectedModCount;//快速故障的凭据
//迭代器初始化的时候让 expectedModeCount 和modCount相同。
//这个就满足了类注释中的“创建迭代器之后的任何时候对列表
//进行结构修改,迭代器将抛出ConcurrentModificationException”这个条件。
Itr() {
    this.expectedModCount = ArrayList.this.modCount;
}
public void remove() {
        //对expectedModeCount重新进行赋值,这就满足了类注释中的
        //“除了通过迭代器自己的remove或add方法,
        //迭代器将抛出ConcurrentModificationException”
        //hasNext和next方法都不会修改expectedModeCount
        this.expectedModCount = ArrayList.this.modCount;
}
//hasNext和next方法都会调用这个方法,
final void checkForComodification() {
    if (ArrayList.this.modCount != this.expectedModCount) {
           throw new ConcurrentModificationException();
    }
}