【Java源码解读】ArrayList

源码来源:OpenJDK 10

 

简述

ArrayList实现了List接口;大小可变;允许有null元素;支持调整其内部用户存储数据的数组大小;与Vector比较相似,除了ArrayList不同步(多线程不安全)。

 

关于性能

  • size, isEmpty, get, set, iterator, listIterator等方法的时间复杂度都是O(1);add的时间复杂度是可变常量(amortized constant time)。(与LinkedList相比,ArrayList的常量因子更小一点)。除了这些操作,其它操作的一般都是线性复杂度O(n)。
  • 当需要向一个ArrayList中添加大量元素时,可先调用ensureCapacity来增大ArrayList内部数组的容量,以减少重新分配内存空间次数

关于线程安全/同步

  • 在多线程场景中使用ArrayList时,如果至少有一个线程会改变ArrayList实例的结构,那么就必须做好同步管理。
  • 添加或删除元素,或显式地改变ArrayList内部的数组,都是会改变ArrayList实例结构的操作。但是仅仅设置其中某个元素的值,不会改变实例结构。
  • 一般可以对拥有ArrayList实例的外部对象进行同步化访问改造,从而达到线程安全的目的。
  • 如果找不到合适的(外部)对象进行同步访问处理,可调用Collections.synchronizedList方法,将ArrayList实例包装成一个线程安全的新对象,再在新对象上执行多线程操作。这个包装操作最好在创建ArrayList实例时就执行,以免在创建和同步化包装这两个操作之间发生误用。
  • 通过ArrayList的iterator方法和listIterator方法得到的迭代器都采用fail-fast机制。也就是说,这样的iterator被创建出来后,如果ArrayList实例的内部结构被改变,iterator就会抛出异常ConcurrentModificationException(除非这个改变是iterator自身的add或remove方法操作的)。
    • 但是并不能保证不同步的并发修改一定会引起上述异常。所以可以将此异常用于辅助排障,但不能让正常处理逻辑依赖于这个fail-fast机制。

构造方法

ArrayList()

将内部数组初始化为一个大小为0数组

 

ArrayList(int initialCapacity)

如果initialCapacity大于0,则将内部数组初始化为一个大小为initialCapacity的数组

如果initialCapacity等于0,则将内部数组初始化为一个大小为0的数组

否则,抛出异常IllegalArgumentException

 

ArrayList(Collection<? extends E> c)

如果c中有元素,则将c中的元素复制到一个新数组中,并将该新数组作为ArrayList的内部数组

否则,将内部数组初始化为一个大小为0的数组

 

关键方法

newCapacity(int minCapacity)

计算内部数组扩容的目标容量

 

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

先取现容量的1.5倍,作为新容量

如果该新容量比现容量大

如果该新容量小于等于最大允许的容量MAX_ARRAY_SIZE,则使用该新容量

否则

如果minCapacity小于0,则抛出异常OutOfMemoryError

否则

如果minCapacity大于MAX_ARRAY_SIZE,就用Integer.MAX_VALUE作为新容量

否则,就用MAX_ARRAY_SIZE作为新容量

否则

如果内部数组是默认的0元素数组,就选DEFAULT_CAPACITY(10)和minCapacity中的较大者作为新容量

如果minCapacity小于0,则抛出异常OutOfMemoryError

否则,将minCapacity作为新容量

MAX_ARRAY_SIZE

值为Integer.MAX_VALUE-8

因为某些VM会在数组头部预留一些空间不放置用户数据,所以需要减8,以防超出VM的限制。

 

其它方法

 trimToSize

将内部数组的容量调整为当前有效元素的数量。可通过此方法来减小ArrayList实例所占用的内存。

该操作会将字段modCount增加1。modCount是父类AbstractList中的字段,它记录了实例内部结构改变的次数。该字段主要由iterator用于实现fail-fast机制。iterator会判定该字段的值是不是其期望的值;如果不是,就会抛出异常。如果AbstractList的子类需要提供fail-fast机制的iterator,则可在会改变其实例内部结构的操作中将该字段增加1;如果不需要提供这样iterator,则可以忽略该字段。

 

ensureCapacity(int minCapacity)

确保内部数组可容纳minCapacity所指定数量的元素

如果minCapacity比内部数组的长度大,且内部数组不是空数组,或minCapacity比初始容量DEFAULT_CAPACITY(10)大],则增大内部数组。具体策略在newCapacity(int minCapacity)中实现

 

public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
            && minCapacity <= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}

 

size()

返回list中元素的个数

 

isEmpty()

判断list中是否存在元素

 

contains(Object o)

判断list中是否存在指定的元素。具体实现就是判断indexOf(o)的返回值是否大于等于0

 

indexOf(Object o)

返回指定元素在list中首次出现的索引。如果不存在目标元素,则返回-1。具体实现就是先判断o是不是null:

如果是null,就挨个检查是否有元素是null,一旦找到就返回相应的索引;

如果不是null,就用o.equals方法挨个检查是否有元素相等,一旦找到就返回相应的索引。

 

lastIndexOf(Object o)

返回指定元素在list中最后一次出现的索引。如果不存在目标元素,则返回-1。具体实现与indexOf(Object o)类似。不同之处是挨个检查的方向相反。

 

clone()

创建一个浅拷贝的副本。也就是说,拷贝的是ArrayList实例对象这个壳,它内部存放的元素不会被拷贝,只是将这些元素的索引复用到了新的list。具体实现就是先执行super.clone()得到一个新的ArrayList实例;再创建一个存放了各元素(引用)的数组,作为新list实例的内部数组;再将modCount的值置为0

 

toArray()

返回一个包含各元素的数组,且这些元素的顺序与list的相同。具体做法就是创建一个list内部数组的副本。因为返回的数组对象是一个全新的对象,所以对此新数组的修改不会影响到原list

 

toArray(T[] a)

与toArray()类似,该方法是泛型形式

如果list的元素能全部放到a数组中,那么各元素(引用)会被放到a中,并返回a。否则,将会创建一个新的数组,数组大小就是当前list的size,并将元素(引用)放到该新数组中,再返回该新数组。

另外,如果a的容量比list中元素个数多,那么a[size]会被置为null。这种做法有助于判断a数组中有效元素的个数。但是需要使用者知道list中不存在null元素。

 

get(int index)

获取指定索引的元素。该方法会先检查index是否大于等于size。如果是,就会抛出异常IndexOutOfBoundsException

 

set(int index, E element)

将指定的元素放到指定的索引处(替换原元素),并返回原元素。该方法也会先检查索引是否越界

 

add(E e)

将指定的元素追加到list的末尾,并返回true。该方法会先将modCount增加1。内部数组已经存满元素,会先调用grow(size+1)进行扩容,再放置新元素。size也会增加1

 

add(int index, E element)

将指定元素插入到list的指定索引处,索引处的原元素及之后的元素都会被向后移动一格(索引值加1)。该方法会先检查索引是否有效。modCount和size都会增加1

 

remove(int index)

移除指定索引处的元素,并返回该元素,索引后的各元素都会被向前移动一格(索引值减1)。该方法会先检查索引是否有效。modCount会增加1,size会减1。此外,内部数组的末尾元素后的一个会被置为null,此做法与toArray(T[] a)类似

 

remove(Object o)

移除list中第一个与指定对象o相等的元素,并返回true(成功移除)或false(未找到相等的元素)。如果o为null,就用‘==null’检查是否相等;如果o不为null,就用o.equals方法。如果找到了相等的元素,就执行与remove(int index)相同的内部逻辑,进行移除操作

 

clear()

移除list中的所有元素。具体实现就是将内部数组的每个有效元素(引用)置为null。modCount会增加1,size会置为0

 

addAll(Collection<? extends E> c)

将指定集合中的所有元素追加到list末尾,并返回true(有新元素加入)或false(没有新元素加入)。具体实现是,先把集合c中的元素(引用)复制到新数组中,再把这些元素(引用)添加到内部数组中。在这个过程中,如果集合c没有元素,会直接返回false;如果原元素个数和新元素个数的和超过了当前内部数组的容量,就会先扩容。另外,无论c中是否有元素,modCount都会增加1。size也会更新(c中有多少个元素就增加多少)

 

addAll(int index, Collection<? extends E> c)

将指定集合中的所有元素追加到指定索引处,并返回true(有新元素加入)或false(没有新元素加入)。该方法会先检查索引是否有效。具体实现与addAll(Collection<? extends E> c)类似。如果指定索引处原来有元素,则从该索引开始向后的其它元素都会被移动最终list的末尾。这与add(int index, E element)类似

 

 

removeRange(int fromIndex, int toIndex)

移除指定索引区间的元素,包括fromIndex的元素,不包括toIndex的元素。toIndex及之后的元素会被前移。该方法会先检查索引是否有效。modCount会增加1

 

removeAll(Collection<?> c)

移除指定集合c中含有的元素,并返回true(有元素被移除)或false(没有元素被移除)。注意:remove(Object o)是移除list中首个相等的元素,而此方法时移除所有相等的元素(不是首个)。因为此方法会对每个元素进行相等性检查(c.contains)。具体实现时,会先找到list中首个相等的元素,再遍历剩余的元素。在这遍历过程中,如果后续有元素是c所不包含,则把该元素(引用)赋到之前的索引处。这个“之前的索引”初始值是首个相等元素的索引,随着遍历过程中需保留元素的增加,该“索引”的值也一同增加。另外,如果在后续遍历过程中发生异常,会先hold住异常,把后方未遍历的元素直接提到前面,然后再重新抛出异常。如果后续遍历一切正常,则modCount增加的值就是被移除的元素个数,并清理现场,即调整size并将新空出来的内部数组空间值置为null

 

listIterator(int index)

获取一个从指定索引开始的ListIterator<E>。该方法会先检查索引是否有效

 

listIterator()

获取一个从0索引开始的ListIterator<E>。这是listIterator(int index)的一个特例

 

iterator()

获取一个从0索引开始的Iterator<E> 

 

subList(int fromIndex, int toIndex)

获取一个指定索引区间的子list(ArrayList.SubList),包括fromIndex处的元素,不包括toIndex处的元素。这个子list只是原list的一个视图,对子list改动会调用原list的方法

 

forEach(Consumer<? Super E> action)

对每个元素应用指定的操作。

 

spliterator()

返回一个Spliterator<E>(可分割迭代器,用于并行遍历)

 

removeIf(Predicate<? super E> filter)

移除所有符合filter的元素。具体实现与removeAll(Collection<?> c)类似,也是先找到第一个符合要求的元素,再遍历后续元素进行移除。但是因为filter具体操作是不受控制的,为了安全,会先遍历所有后续元素,并在一个数组中记录符合filter的元素的索引信息,然后再执行真正的移除操作,最后清理现场。modCount会增加1,size也会改变。

 

removeIf(Predicate<? super E> filter, int i, final int end)

移除指定索引区间内所有符合filter的元素,包括i,不包括end。这是removeIf(Predicate<? super E> filter)的一个特例

 

replaceAll(UnaryOperator<E> operator)

将所有元素替换为将operator处理过后得到的结果。modCount会增加1

 

sort(Comparator<? super E> c)

使用指定的比较器,对所有元素进行排序,排序结果反映在原内部数组

 

其它

  • 可以阅读源码,体会一下使用final局部变量的场景。
  • ArrayList中存在部分可以改进的代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值