容器源码通读之ArrayList

GitHub: KolinHuang
个人博客:KolHuang Blog
欢迎交流~

0.写在前面

今年秋招形式严峻,为了打好基础应对明年的秋招,打算把面试中经常会考察的知识点全部整理一遍。
从这篇开始,基本会把主流容器的代码从头到尾看一遍,把一些细节以及有趣的地方摘出来,以便后续复习。

1.类前注释

先看源码开始的一大段注释,基本上把ArrayList的功能和实现介绍了一下:

/**
Resizable-array implementation of the List interface.  Implements
all optional list operations, and permits all elements, including
null.  In addition to implementing the List interface,
this class provides methods to manipulate the size of the array that is
used internally to store the list.  (This class is roughly equivalent to
Vector, except that it is unsynchronized.)
 List 接口的可调整大小的数组实现。 实现所有可选的列表操作,并允许所有元素,包括null。
除了实现List接口之外,此类还提供一些方法来操纵内部用于存储列表的数组的大小。 (此类与Vector大致等效,但它是不同步的。)
划重点!可调整大小!提供了一些方法来操纵数组的大小!
 *
The size, isEmpty, get, set,
iterator, and listIterator operations run in constant
time.  The add operation runs in amortized constant time,
that is, adding n elements requires O(n) time.  All of the other operations
run in linear time (roughly speaking).  The constant factor is low compared
to that for the LinkedList implementation.
size isEmpty,get,set,iterator和listIterator方法在恒定时间内运行。
 添加方法以固定的时间运行,也就是说,添加n个元素需要O(n)时间。 
 所有其他操作均以线性时间运行(大致而言)。 与LinkedList实现相比,常数因子较低。
 *
Each ArrayList instance has a capacity.  The capacity is
the size of the array used to store the elements in the list.  It is always
at least as large as the list size.  As elements are added to an ArrayList,
its capacity grows automatically.  The details of the growth policy are not
specified beyond the fact that adding an element has constant amortized
time cost.
每个ArrayList实例都有一个容量。 容量是用于在列表中存储元素的数组的大小。 
它总是至少与列表大小一样大。 将元素添加到ArrayList时,其容量会自动增长。
 除了添加元素具有固定的摊销时间成本外,没有指定增长策略的详细信息。

 *
An application can increase the capacity of an ArrayList instance
before adding a large number of elements using the ensureCapacity
operation.  This may reduce the amount of incremental reallocation.
应用程序可以通过使用sureCapacity方法在添加大量元素之前增加ArrayList实例的容量。 这可以减少增量重新分配的数量。
sureCapacity这个方法是做什么的,以及如何做的?
 *

<strong>Note that this implementation is not synchronized.</strong>
If multiple threads access an ArrayList instance concurrently,
and at least one of the threads modifies the list structurally, it
must be synchronized externally.  (A structural modification is
any operation that adds or deletes one or more elements, or explicitly
resizes the backing array; merely setting the value of an element is not
a structural modification.)  This is typically accomplished by
synchronizing on some object that naturally encapsulates the list.
请注意,此实现未同步。 如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。
(结构修改是添加或删除一个或多个元素或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)
通常,通过在自然封装列表的某个对象上进行同步来完成此操作。


 *
If no such object exists, the list should be "wrapped" using the
{@link Collections#synchronizedList Collections.synchronizedList}
method.  This is best done at creation time, to prevent accidental
unsynchronized access to the list:<pre>
  List list = Collections.synchronizedList(new ArrayList(...));</pre>
如果不存在这样的对象,则应使用{@link Collections#synchronizedList Collections.synchronizedList}方法“包装”列表。
最好在创建时完成此操作,以防止意外的不同步访问列表:
List list = Collections.synchronizedList(new ArrayList(...));

 *
 <a name="fail-fast">
 The iterators returned by this class's {@link #iterator() iterator} and
 {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
 if the list is structurally modified at any time after the iterator is
 created, in any way except through the iterator's own
 {@link ListIterator#remove() remove} or
 {@link ListIterator#add(Object) add} methods, the iterator will throw a
 {@link ConcurrentModificationException}.  Thus, in the face of
 concurrent modification, the iterator fails quickly and cleanly, rather
 than risking arbitrary, non-deterministic behavior at an undetermined
 time in the future.
 在创建迭代器之后的任何时间如果列表在结构上有任何修改,
则此类的{@link #iterator()迭代器}和{@link #listIterator(int)listIterator}方法返回的迭代器会fail-fast。
除了通过迭代器自己的{@link ListIterator#remove()remove}或{@link ListIterator#add(Object)add}方法)之外,
迭代器都会抛出{@link ConcurrentModificationException}。 
因此,面对并发修改,迭代器会快速抛出异常,不会冒任何风险。

 *
Note that the fail-fast behavior of an iterator cannot be guaranteed
as it is, generally speaking, impossible to make any hard guarantees in the
presence of unsynchronized concurrent modification.  Fail-fast iterators
throw {@code ConcurrentModificationException} on a best-effort basis.
Therefore, it would be wrong to write a program that depended on this
exception for its correctness:  the fail-fast behavior of iterators
should be used only to detect bugs.

注意,不能保证迭代器的快速失败行为,
因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。
快速失败的迭代器会尽最大努力抛出{@code ConcurrentModificationException}。 
因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
就是说不能依赖这个异常来编写任何相关判断性的程序片段,这个异常的行为是不确定的,有可能发生了结构性修改,但是没有抛出异常。

从上面的注释可以看出几个要点:

  • ArrayList支持扩容。

  • ArrayList支持添加null元素。

  • 应用程序可以通过使用sureCapacity方法在添加大量元素之前增加ArrayList实例的容量。 这可以减少增量重新分配的数量。

  • 这个容器实现是非同步的,如果多个线程访问ArrayList实例,就需要在外部进行同步。注释中说可以用下面这种方法:

    List list = Collections.synchronizedList(new ArrayList(...));
    
    • 但是最好还是不要用这个方法,效率太低,如果有需要可以换成并发List:CopyOnWriteArrayList
  • 创建迭代器之后,出了迭代器自带的removeadd方法之外,其他对列表有任何结构性修改的行为都会导致程序抛出ConcurrentModificationException异常。但是不能保证一定会抛出这个异常,所以不能根据这个异常来编写任何判断性的程序片段。

2.成员变量

这个类开头定义了如下变量(部分):

private static final long serialVersionUID = 8683452581122892189L;//序列化ID
//初始化时的默认容量。
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例。这个没懂是啥
private static final Object[] EMPTY_ELEMENTDATA = {
   };


//共享的空数组实例,用于默认大小的空实例。 我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要膨胀多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
   };

/**
存储ArrayList元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度。 
添加第一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList
都将扩展为DEFAULT_CAPACITY。
*/
transient Object[] elementData; //非私有以简化嵌套类访问
private int size;
//支持的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

几个要点:

  • 当初始化时未指定容量,默认的容量为10。

  • EMPTY_ELEMENTDATA用于有参构造器,参数为0的初始化;DEFAULTCAPACITY_EMPTY_ELEMENTDATA用于无参构造器的初始化。这两个final static的数组实例只用于0容量的初始化,在往List里添加元素的时候,就会被替换掉,扩容为默认大小10。所以这俩家伙存在的意义是啥呢?提升初始化效率?

  • elementData就是用来存列表元素的数组,定义为非私有,来简化嵌套类的访问。使用了关键字transient。简单地介绍一下:

    • 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
    • transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
    • 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
    • 在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量这与是否被transient修饰无关。
    • size变量记录列表中元素的个数,在并发环境下会有误差,没啥用。
  • 最后,有些博客中居然说ArrayList的底层数组没有容量上限,代码中明确规定了最大上限是Integer.MAX_VALUE - 8。其实这里的上限有可能可以超出这个数字,这要按不同JVM的实现确定,所以如果有必要的需求,源码中会让最大容量达到Integer.MAX_VALUE,只不过大多数的JVM会在容量超过Integer.MAX_VALUE - 8时,产生OutOfMemoryError。源码这样实现的目的就是为了适配所有的JVM,另一个目的是为了不在扩容的时候产生内存超出错误,保护程序运行安全。这方面的不详细分析可以看这篇文章的最后部分:从算法到源码:PriorityQueue源码学习

2.1关于modCount变量

几乎在所有线程不安全的容器中都有这么一个变量modCount,记录了修改容器结构的操作次数。例如在ArrayList中,modCount在迭代器实现中使用:

/**
     * An optimized version of AbstractList.Itr
     */
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;

  public void remove() {
   
      ...
      checkForComodification();

      try {
   
        ...
        expectedModCount = modCount;
      }...
  }

  @Override
  @SuppressWarnings("unchecked")
  public void forEachRemaining(Consumer<? super E> consumer) {
   
      <
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值