ArrayList源码分析

ArrayList源码分析

其继承了AbstractList类,并实现了Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess这些接口

其不是线程安全的

VectorList的古老实现类,其是线程安全的

一. 构造函数

1. 默认构造函数

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

默认构造函数会初始化DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组为保存数据的数组。

这样在之后的第一次add操作时,会判断是否当时由默认构造函数构造,这样就会将10作为扩容的最小容量。

2. 初始化容量构造

private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else {
        if (initialCapacity != 0) {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }

        this.elementData = EMPTY_ELEMENTDATA;
    }

}

若初始化容量大于0,则分配一个初始容量大小的Object数组

若初始容量为0,则将EMPTY_ELEMENTDATA赋值作为存储数据的数组

不直接新建一个空数组是为了后面扩容时分辨当时是使用默认构造函数构造还是参数为0的构造函数构造

3. 从给定集合构造

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((this.size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            this.elementData = a;
        } else {
            this.elementData = Arrays.copyOf(a, this.size, Object[].class);
        }
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }

}

若集合为空,则赋值EMPTY_ELEMENTDATA

二. 扩容

当向ArrayList中添加元素的时候,若当前空间足够放下新的元素,则不用扩容,直接添加进数组中的相应位置,若当前空间大小不足够放下新的元素,则需要对原数组进行扩容,且为了减少扩容频率,每次不能只扩容刚好放下新元素的大小,而是需要以1.5倍的容量大小扩容。

1. add方法

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

其中调用了ensureCapacityInternal方法,调用该方法会决定是否需要扩容,调用之后才将新的数据添加到数组的末尾。

2. ensureCapacityInternal方法

private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

在该方法中,会判断当时的构造方法,如果是无参数构造,即elementdataDEFAULTCAPACITY_EMPTY_ELEMENTDATA时,会以需求的最小容量与DEFAULT_CAPACITY中的最大值作为新的最小容量,也就是这种情况下会从10开始每次1.5倍扩容。如果当初指定了size为0的构造函数,则最小容量不变,也就是会从1开始每次1.5倍扩容。

在该函数中会调用ensureExplicitCapacity来判断是否需要进行扩容。

3. ensureExplicitCapacity方法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

在这里modCount会加1,该变量用来标记ArrayList实例的操作次数。

同时若需要扩容,则会调用最终的扩容函数,并传入实际需要的最小容量。

grow方法

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

在该方法中,会先计算现在的容量的1.5倍大小是否满足最小容量,这里使用位运算得到1.5倍结果。

如果不满足,则直接以最小需求容量作为扩容后的大小。

同时会判断新的数组大小是否超过预设的最大容量,使用hugeCapacity方法判断并抉择,若最小需求容量比最大容量大则直接分配Integer.MAX_VALUE大小的数组,否则分配预设最大容量的数组。

这里在hugeCapacity中用最小需求容量来比较是因为之前计算的新的数组大小可能是通过1.5倍运算得来的结果,这样其可能远远大于最小需求容量,也因此可能导致新的容量大于数组大小限制,因此使用最小需求容量重新计算新的数据容量

之后便使用Arrays.copyOf函数复制原数组大小并分配新数组。

ensureCapacity方法

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if real element table
        ? 0
        // larger than default for empty table. It's already supposed to be
        // at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

该方法是为了避免频繁扩容时,在将要添加大量元素之前进行调用,先进行一次扩容,以免后面的多次扩容影响效率。

三. modCount

ArrayList中的每次增删操作中,都会有一个modCount变量自增,其记录了该ArrayList实例的操作次数。

该变量来自于父类AbstractLIst,其用于防止在迭代过程中的同时发生了结构性变化比如添加删除。

private class Itr implements Iterator<E> {
    int cursor = 0;
    int lastRet = -1;
    int expectedModCount;

    private Itr() {
        this.expectedModCount = AbstractList.this.modCount;
    }

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

    public E next() {
        this.checkForComodification();

        try {
            int i = this.cursor;
            E next = AbstractList.this.get(i);
            this.lastRet = i;
            this.cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException var3) {
            this.checkForComodification();
            throw new NoSuchElementException(var3);
        }
    }

    public void remove() {
        if (this.lastRet < 0) {
            throw new IllegalStateException();
        } else {
            this.checkForComodification();

            try {
                AbstractList.this.remove(this.lastRet);
                if (this.lastRet < this.cursor) {
                    --this.cursor;
                }

                this.lastRet = -1;
                this.expectedModCount = AbstractList.this.modCount;
            } catch (IndexOutOfBoundsException var2) {
                throw new ConcurrentModificationException();
            }
        }
    }

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

AbstractList的迭代器由Itr内部类实现,其有一个expectedModCount保存期待的modCount值。这样当两个值不一样时,说明该数组的元素个数发生了变化,继而抛出错误,但是使用迭代器的remove方法就不会出现该问题。

测试代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                if(str.equals("e")){
                    list.remove(str);
                }else{
                    System.out.println(str);
                }
            }
    }
}

这里使用ArrayListremove方法,运行会抛出错误:

a
b
c
d
Exception in thread "main" java.util.ConcurrentModificationException
        at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
        at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
        at demo.main(Main.java:15)

但是换用iterator.remove()就正常输出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值