JavaSE基础知识(二十一)--Java集合(容器)之类ArrayList的源码分析

Java SE 是什么,包括哪些内容(二十一)?

本文内容参考自Java8标准
随机访问列表的一个常用实现类:

ArrayList

类声明:

   public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
   {
   
      ...
   }

源代码图示(图下面有解释说明):
类ArrayListList源码英文注释分别阐述了以下内容(一个段落用一个小标):
⑴、类ArrayList是一个实现了接口List,底层实现是数组的一种列表。它有以下特点:
①、能根据需要调整自身容量的大小,完全解除了数组一旦创建就不能改变自身大小的限制(它提供了一些调整底层数组的方法,实际上,看完了本篇博文你会发现,它仍然没有改变数组的大小,只是在需要的时候会重新创建一个新的数组,再将所有的元素复制到新数组中)。
②、实现了接口List的所有方法,能存储任何元素,包括null。
③、与类Vector类似(类Vector是线程安全的),但是不是线程安全的。
⑵、类ArrayList的以下方法的执行时间是一个常数(也就是一个固定的值,和存储元素的多少没有关系):size()、isEmpty()、get() 、set() 、iterator() 、listIterator()。
方法add()的时间也是固定的,即添加一个元素的时间都是一样的。如果添加一个元素的时间为t,那么添加n个元素的时间则为nt。剩余其它方法的运行时间都是和元素个数成线性相关(也就是线性关系,如果元素的个数为n,每个元素的操作时间为t,那么连续操作n个元素的总时间y=nt+a,a是一个固定的常量,这个就是线性关系)。
类ArrayList与另一个列表类LinkedList相比,常数因子较低。也就是ArrayList较简单,在某些操作上花费的时间较少。
⑶、每个ArrayList实例对象都会用一个int类型的变量来存储当前列表底层数组的空间大小。它的值总是大于等于当前列表所存储的元素个数。随着新的元素对象添加到ArrayList实例对象中,这个表示数组存储空间的值会自动增长(实际就是一个更长的新数组替换了老的数组)。
⑷、在需要向一个ArrayList实例对象添加大量的元素之前,可以通过它的方法ensureCapacity(int minCapacity)一次性创建足够的存储空间,这样可以减少多次添加造成的内存开销(比如可能需要分三次添加9个元素,第一次添加3个,第二次添加3个,第三次添加3个,如果直接调用add方法添加,那么当前ArrayList对象实例会在第一次添加之前创建一个3个空间的数组,然后添加元素。在第二次添加之前会重新创建一个6个空间的数组,将之前的3个元素复制到它里面后再将之前的3个空间的数组对象丢弃,然后添加元素。在第三次添加之前会重新创建一个9个空间的数组,将之前的6个元素复制到它里面后再将之前的6个空间的数组对象丢弃,然后添加元素。方法ensureCapacity(int minCapacity)存在的意义是,在这种情况下,你可以使用它直接创建一个9个空间的数组,再进行添加元素的操作)。
⑸、类ArrayList的删除或者添加元素的方法实现未进行线程同步。如果有多个线程同时访问同一个ArrayList对象实例,并且至少有一个线程进行了删除或者添加元素的操作,那必须在它的外部进行线程同步。通常,通过自然封装列表的某个对象进行同步来完成此操作(比如通过关键字synchronize或者类Lock等方式)。
⑹、如果不存在这样一个自然封装列表的对象,则应该使用以下方式对ArrayList对象实例进行同步(通过类Collections的方法synchronizedList(new ArrayList(…))):

// 对ArrayList列表对象进行同步
   //通过类Collections的方法synchronizedList(List list)对
   //ArrayList列表对象进行同步
   List list = Collections.synchronizedList(new ArrayList(...));

最好是在创建ArrayList对象实例的时候就完成同步操作,可以防止对它的意外非同步访问。
⑺、类ArrayList的迭代器Iterator以及ListIterator都实现了快速失败机制(fail-fast),如果在创建了迭代器对象后,通过除迭代器对象本身提供的其它方法修改了列表结构(比如添加、删除元素等),将会引发ConcurrentModificationException异常(如果是通过迭代器对象本身提供的方法修改列表结构,则不会引发这个异常)。
⑻、类ArrayList不能完全保证快速失败机制一定会执行,通常来说,如果在不同步的情况下并发修改一个列表,快速失败机制无法做出任何严格的保证。 但是实现了快速失败机制的迭代器会尽最大努力抛出ConcurrentModificationException异常。因此,完全依赖快速失败机制来保证程序的正确性是错误的,快速失败机制应该仅用于检测错误。
⑼、类ArrayList是集合(容器)框架重要的一员。
下面进行逐行的源代码分析:

1、变量

⑴、serialVersionUID

      //static表示属于类(与对象实例无关),final表示一旦赋值就不可变.
      //long表示数据类型,它对于类的作用就好比身份证对于人的作用,主要用于序列化与
      //反序列化时的版本控制.
      private static final long serialVersionUID = 8683452581122892189L;

⑵、DEFAULT_CAPACITY

      //static表示属于类(与对象实例无关),final表示一旦赋值就不可变,
      //int表示数据类型,初始化值为10.
      //它的作用是:如果使用类ArrayList的无参构造方法创建一个
      //对象实例,它就是默认的数组大小.
      //也可以这么说,在创建类ArrayList的对象实例的时候,一定要提供一个参数
      //来指定底层数组的大小,如果未提供,就默认是DEFAULT_CAPACITY。
      private static final int DEFAULT_CAPACITY = 10;

⑶、EMPTY_ELEMENTDATA

      //static表示属于类(与对象实例无关),final表示一旦赋值就不可变,
      //Object[]表示数据类型,初始化值为{},也就是一个空数组对象.
      //它的作用是:为类ArrayList接下来的代码中的初始化做准备
      //你肯定会有疑问,为什么不在需要使用它的时候再创建,而是提前创建做准备?
      //主要是为了后期的代码维护,如果哪里需要就在哪里创建会造成代码分散,维护成本过高
      //提前创建后在需要的时候就直接赋值,而需优化修改的时候,仅修改这里一处就行
      //已使用它的所有地方都能同步享受优化后的便利
      private static final Object[] EMPTY_ELEMENTDATA = {
   };

⑷、DEFAULTCAPACITY_EMPTY_ELEMENTDATA

      //static表示属于类(与对象实例无关),final表示一旦赋值就不可变.
      //Object[]表示类型,初始化值为{},也就是一个空数组对象.
      //它与上面的EMPTY_ELEMENTDATA一样,唯一不同的是,当第一个元素添加进来以后
      //它知道如何扩张(对应ArrayList的存储空间自动增长)
      private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
   };

为什么同时存在EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA两个空数组,需要去对比JDK1.7的ArrayList源码。这是JDK1.8做的一个性能优化。
⑸、elementData

      //为了简化嵌套类的访问,它不是private的.
      //用关键字transient修饰表示如果序列化当前类会将这个变量排除在外
      //Object[]表示类型
      //它的作用是:每个ArrayList对象实例持有的底层数组的引用
      //在这里,它仅仅代表了一个数组的引用,因为并未初始化。
      transient Object[] elementData;

⑹、size

      //int表示数据类型,你需要注意的是:没有将它初始化。
      //它的作用是:存储当前ArrayList对象实例所拥有的元素的数量
      //也可以这么说,size的值始终表示当前ArrayList对象实例存储的元素个数
      private int size;

2、构造方法(这里需要提一个前提:ArrayList的所有构造方法都必须要提供一个int类型的参数来指定底层数组的大小,无参的构造方法使用默认的大小,也就是10)

⑴、构造方法一(带一个int类型的形式参数initialCapacity)

      //构造方法一,带一个int类型的形式参数int initialCapacity.
      //它的作用是:
      //创建一个具有指定初始大小initialCapacity的ArrayList对象实例。
      public ArrayList(int initialCapacity) {
   
         //如果initialCapacity的值大于0
         if (initialCapacity > 0) {
   
            //创建一个具有initialCapacity存储空间的Object类型的
            //数组对象实例,将它赋值给引用elementData
            this.elementData = new Object[initialCapacity];
         //如果initialCapacity的值等于0
         } else if (initialCapacity == 0) {
   
            //直接将EMPTY_ELEMENTDATA赋值给对象引用elementData
            this.elementData = EMPTY_ELEMENTDATA;
            //实际上,这里也可以这么写
            //this.elementData = new Object[] {};
            //或者this.elementData = {};
            //但是一般不会这么做
         } else {
   
            //如果initialCapacity的值小于0,直接抛出IllegalArgumentException
            //异常。同时输出非法的initialCapacity值的提示信息:
            //"Illegal Capacity: "+initialCapacit
            throw new IllegalArgumentException("Illegal Capacity: "+
                                                  initialCapacity);
         }
      }

在这个有参构造方法中你会发现,当参数为0的时候,初始化使用的是EMPTY_ELEMENTDATA。
⑵、构造方法二不带任何的形式参数)

      //构造方法二,不带任何形式参数
      public ArrayList() {
   
         //直接将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给对象引用elementData
         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }

在这个无参构造方法中你会发现,初始化使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
⑶、构造方法三(带一个Collection<? extends E>类型的形式参数c)

      //构造方法三,带一个Collection<? extends E>类型的形式参数c,
      //它的作用是:创建一个包含了指定集合(容器)c所有元素的列表。此列表中元素的顺序
      //与指定集合(容器)c中的完全一致。
      public ArrayList(Collection<? extends E> c) {
   
         //将集合(容器)c转换成数组,
         //然后赋值给elementData
         elementData = c.toArray();
         //如果转换得到的数组长度不为0
         if ((size = elementData.length) != 0) {
   
            //c.toArray不能保证一定返回正确的结果.所以这里需要进一步判断。
            //c.toArray()理论上应该返回Object[]类型。然而使用类
            //Arrays的方法asList(T...a)得到的list,其toArray()方法返回的数组却
            //不一定是Object[]类型的,而是返回它本来的类型。
            //例如Arrays.asList(String)得到的list,
            //使用toArray会得到String[]类型。
            //如果向这个数组中存储非String类型的元素,
            //就会抛出ArrayStoreException异常
            //这是一个官方bug,可以通过以下地址查看
            //https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652
            //如果elementData的类型不是Object[]
            //类型(方法getClass()获取当前引用的类型)。
            if (elementData.getClass() != Object[].class){
   
               //强制将elementData转换成Object[]类型。
               //具体可以查看类Arrays的方法public static <T,U> T[] copyOf
               //(U[] original, int newLength, Class<? extends T[]> newType)
               elementData = Arrays.copyOf(elementData, size, Object[].class);
            如果转换得到的数组长度为0
            } else {
   
            //则直接将EMPTY_ELEMENTDATA赋值给elementData.
            this.elementData 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值