java 集合 ArrayList

关于虚拟机垃圾回收的部分还没写完,感觉理解的不是很好,所以最近暂时先着手JDK源码分析的部分,今年是进阶之年,务必把Java的基础打牢,把曾经拖欠的东西全都补回来,废话不多说,下面开始我的阅读源码之路,希望自己对Java设计的理解能够更上一层楼,也希望能对其他人产生一点点的作用~

读源码系列文章的套路是这样的:

  • 首先:确定该类在JDK核心类库中继承结构的位置。
  • 然后:解释源码中最上边的类注释,就是那个动辄几十上百行的注释,不要忽视这些注释,它能够帮助我们快速地了解这个类的基本属性(如底层实现机制,是否线程安全),同时会给我们一些使用时的最佳实践,这些对于我们理解各类的特性有着很大的帮助。
  • 接下来:分析类的核心属性和方法(主要是方法,因为通常属性是私有的,我们无法直接接触到)。
  • 最后:用几个**MethodTest类,来展示我们阅读的源码类的重要方法的使用。

ArrayList源码解析

ArrayList继承自抽象类AbstractList,同时实现了List, RandomAccess, Cloneable, java.io.Serializable接口,作为Collection一派,ArrayList在一般的java web开发中的出场率是非常高的,它实现了可以动态扩展的数组,并且兼容性非常强(什么都能装),但是有一些小细节,在我们不注意的情况下,还是灰常容易出错误的~

首先我们逐段来看一下ArrayList源码中的类注释:

类注释

 * Resizable-array implementation of the <tt>List</tt> interface.  Implements
 * all optional list operations, and permits all elements, including
 * <tt>null</tt>.  In addition to implementing the <tt>List</tt> 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
 * <tt>Vector</tt>, except that it is unsynchronized.)

 

ArrayList是List接口的一种可变长度数组方式的实现,有点拗口吗?这么理解,ArrayList的底层实现机制是数组,而LinkedList的底层实现机制是链表,这样是不是就很好理解了?那么好,接着读,它可以装载所有类型的元素,包括null,这里有一点需要注意,所有类型不包括基本数据类型,如果你向ArrayList中添加一个基本数据类型(比如int型的56),那么它会被自动包装为对应的包装类(这里对应的应该就是Integer)类,为什么会这样,一会就知道了。

 * <p>The <tt>size</tt>, <tt>isEmpty</tt>, <tt>get</tt>, <tt>set</tt>,
 * <tt>iterator</tt>, and <tt>listIterator</tt> operations run in constant
 * time.  The <tt>add</tt> operation runs in <i>amortized constant time</i>,
 * 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 <tt>LinkedList</tt> implementation.

 

size,isEmpty,get,set,iterator,listIterator方法的运行时间为常数时间(constant time),不会因为list元素数量的增长而变大,这是因为上述方法其实就是操作内部数组的索引,所以时间是固定的。

add方法的运行时间为摊还运行时间(amortized constant time),不懂摊还运行时间是什么意思不要紧,下一句就解释了:添加n个元素需要O(n)的时间,说白了时间复杂度就是线性阶,其他的方法的运行时间也是线性时间,我没明白为什么add方法跟其他方法要分开说,有大神懂的请告知小弟,感激不尽~

 * <p>Each <tt>ArrayList</tt> instance has a <i>capacity</i>.  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实例都会有一个capacity属性(容量),用以表示当前list里,存储数据的数组的大小,list的capacity大于等于list的size(size是当前实际存储元素的个数),向数组中添加元素时,capacity会自动增长(原来的capacity不够的话),在添加元素花费摊还常数时间的基础上,增长策略的细节并未被指定。

 * <p>An application can increase the capacity of an <tt>ArrayList</tt> instance
 * before adding a large number of elements using the <tt>ensureCapacity</tt>
 * operation.  This may reduce the amount of incremental reallocation.

 

在大批量插入元素之前,可以使用ensureCapacity方法来预先增大list容量,这样可以减少因为扩容而重新分配的次数,这点比较好理解,因为底层实现是数组嘛,数组长度是固定的,而一旦扩容,势必是重新定义了一个数组,此之谓“重新分配”。

 * <p><strong>Note that this implementation is not synchronized.</strong>
 * If multiple threads access an <tt>ArrayList</tt> instance concurrently,
 * and at least one of the threads modifies the list structurally, it
 * <i>must</i> 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是非同步的,当多个线程并发访问ArrayList实例,并且至少其中某个线程在结构上修改(结构上修改,指添加/删除了某些元素,或者显示地修改了内部数组的大小,而不是修改某个元素的值)了此实例的话,必须对在外部对实例进行同步操作,同步可以是某个包装了此实例的一个同步对象(synchronizing-object),如果没有的话,那么在新建这个ArrayList实例的时候,应该用如下方式创建该实例:

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

 * <p><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>:
 * 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.
 *
 * <p>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:  <i>the fail-fast behavior of iterators
 * should be used only to detect bugs.</i>

 

这段不结合实例的话理解起来比较吃力,后面重点介绍的也是ArrayList里面的iterator()方法,先硬翻译一下:通过iterator()和listIterator(int)方法返回的迭代器存在一种fail-fast机制,fail-fast机制具体表现为,当list实例的迭代器被创建之后,任何除迭代器本身的remove和add方法的方法对此list结构上的修改,都会导致该迭代器抛出ConcurrentModificationException异常。因此,在面对多线程并发修改的时候,迭代器会快速地完全失效(直译为失败,但是我感觉失效更符合编程语言),而不是冒在将来不确定的时间放生不确定行为的危险。

注意,迭代器的fail-fast机制不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。fail-fast的迭代器尽最大努力抛出 ConcurrentModificationException 。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的fail-fast机制应该仅用于检测程序错误。

OK,以上就是JDK1.7中ArrayList的类注释部分,概要总结如下:

  • ArrayList可以装载所有元素类型,包括null值
  • ArrayList底层实现为数组,它跟Vector很相似,但是前者是非线程安全的
  • ArrayList的增长会导致自动扩容(本质是new一个新的数组),因此大批量插入数据之前可以预先将ArrayList扩容(ensureCapacity方法),以防止多次ArrayList自身扩容所带来的性能损耗。
  • 创建list实例的迭代器之后就不能再对list结构上进行修改(迭代器本身的add和remove方法除外),否则迭代器会抛出ConcurrentModificationException异常。

属性

读完类注释,那么接下来,就进入ArrayList的内部,看看其究竟是如何实现的吧~首先是两个最重要的属性!

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    private transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

ArrayList的根出现了!!!!!!看到上边的elementData了吗,它就是ArrayList实例最终存储数据的地方,说ArrayList基于数组实现也是因为它喽。
下边的size就是list实例实际存储元素的个数啦,它是小于等于上边elementData数组的大小的。

posted on 2017-11-20 20:14  忘-乐  阅读(166)  评论(0)    收藏  举报

导航