JDK1.8--ArrayList

java中集合是重要的部分之一,在实际工作过程也被高频率使用,这里大概记录一下1.8中的ArrayList,主要分析一下几个常用的方法,扩容机制,以及多线程下引起的线程安全问题,纯属个人拙见,不足之处还请评论支出,共同学习进步!

1.对于集合整体架构如下图所示:

 通过源码我们可以看到ArrayLis的底层数据结构是数组,这也赋予了它检索效率高的优势,但是由于数组的局限性,达到一定容量时需要扩容,为了减少扩容带来性能降低,我们在使用ArrayList初始化时可以根据使用场景预估其长度而定一个初始容量值,这样可以减少频繁扩容而带来不必要的开销。

2.常用方法

构造方法

通过注释我们可以看出来三个构造方法适用于不同的场景,

/**
 * Constructs an empty list with the specified initial capacity.
 * 初始容量值的构造方法
 */
public ArrayList(int initialCapacity);

/**
 * Constructs an empty list with an initial capacity of ten.
 * 无参构造方法,初始容量为10
 */
public ArrayList();

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 * 构建一个包含具体集合元素的构造方法,它们的顺序依次通过集合的迭代器返回
 */
public ArrayList(Collection<? extends E> c);

添加元素方法

  • 一个参数的add方法

/**
 * Appends the specified element to the end of this list.
 * 在list的末尾增加元素e
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

我们先来说说ArrayList的两个重要参数,如注释

/**
 * Shared empty array instance used for empty instances.
 * 用于空实例的空数组实例
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 * 用于默认大小空实例的空数组实例,我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要扩容多少。
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

我们继续跟进add方法

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) {
    // 操作数记录变量,Java快速报错机制后续分析
    modCount++;

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

calculateCapacity这个方法就利用了如上的两个参数,如果是使用默认无参构造方法也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组对象时,增加第一个元素E e返回的是默认的容量长度DEFAULT_CAPACITY,在进入ensureExplicitCapacity方法后,经过if判断后进行扩容,执行grow方法,我们继续看grow方法

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 * 增加容量以确保它至少可以容纳最小容量参数指定的元素数量。简而言之就是扩容
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 右移1位操作,等效于oldCapacity/2,但是位移是底层操作更高效
    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;
}

对于扩容有几种情况:

  • 如果使用默认构造方法,增加第一个元素时minCapacity = 10,经过grow方法后数组elementData从0扩容到10,后续再增加元素扩容时则是1.5倍的扩容
  • 如果使用初始值构造方法,但是初始容量为0时,增加第一个元素minCapacity=1,经过grow方法后数组elementData从,0扩容到1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容
  • 当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError异常

至此ArrayList扩容机制分析完毕,扩容完成后,添加新元素E e。

在ensureExplicitCapacity方法中有个一个增量modCount,这里是利用了java的快速报错机制,也算是一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果迭代遍历比如使用迭代器Iterator(ListIterator)或者forEach,会将modCount变量值传给exceptedModCount,并且会检查modCount与exceptedModCount是否相等,如果此时对同一个容器执行新增删除或者修改操作,modCount会改变,二者不相等则会抛出ConcurrentModificationException异常。但是如果使用Iterator的remove方法则不会发生此异常。举例如下:

 

 剩下的三个添加元素的方法在此不再赘述。

此篇主要内容到此结束,有不足之处还请大家指正,谢谢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章