• ArrayList源码学习


         本文依照构类定义、造函数、成员变量、方法的顺序进行分析。

    一、ArrayList数据结构

      通过翻阅源码和《算法》书籍,我们知道ArrayList的底层数据结构就是数组。在源码中通过object elementData[ ]数组来表示了底层结构。我们对ArrayList类的实例的所有操作底层其实都是基于数组的。

    二、ArrayList源码分析

      2.1 类定义

    1 public class ArrayList<E> extends AbstractList<E>
    2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    View Code

      解释:ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝,实现该接口,就可以使用Object.clone()方法了)、Serializable(可序列化)。

            1)为什么中间要多一个AbastractList抽象父类,是因为这个类中包含了部分通用实现,这样写具体类,比如ArrayList时,只需要专注于本类的具体实现了。

            2)在查看了ArrayList的父类AbstractList也实现了List<E>接口,那为什么子类ArrayList还是去实现一遍呢?百度半天结果是因为author在设计的时候的错误而已,因为没啥影响,所以就没管它了,继续保留了。

      2.2 类的成员变量  

     1 private static final long serialVersionUID = 8683452581122892189L;//版本号
     2 
     3     /**
     4      * Default initial capacity.
     5      */
     6     private static final int DEFAULT_CAPACITY = 10;  // 缺省容量
     7 
     8     /**
     9      * Shared empty array instance used for empty instances.
    10      */
    11     private static final Object[] EMPTY_ELEMENTDATA = {}; // 空对象数组
    12 
    13     /**
    14      * Shared empty array instance used for default sized empty instances. We
    15      * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
    16      * first element is added.
    17      */
    18     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 缺省空对象数组
    19 
    20     /**
    21      * The array buffer into which the elements of the ArrayList are stored.
    22      * The capacity of the ArrayList is the length of this array buffer. Any
    23      * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    24      * will be expanded to DEFAULT_CAPACITY when the first element is added.
    25      */
    26     transient Object[] elementData; // non-private to simplify nested class access
    27 
    28     /**
    29      * The size of the ArrayList (the number of elements it contains).
    30      *
    31      * @serial
    32      */
    33     private int size;//实际元素大小,默认为0
    34 /**
    35      * The maximum size of array to allocate.
    36      * Some VMs reserve some header words in an array.
    37      * Attempts to allocate larger arrays may result in
    38      * OutOfMemoryError: Requested array size exceeds VM limit
    39      */
    40     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大数组容量
    View Code

          解释:该类核心为elementData,它代表了ArrayList的底层数据结构形式,类型为Object[],实际元素会存放在里面,并且被transient修饰,表明在序列化的时候,此字段是不会被序列化的。

      2.3 类的构造函数

      该类总共有三个构造函数,分别如下:

           (1) public ArrayList(int initialCapacity)

     1 /**
     2      * Constructs an empty list with the specified initial capacity.
     3      *
     4      * @param  initialCapacity  the initial capacity of the list
     5      * @throws IllegalArgumentException if the specified initial capacity
     6      *         is negative
     7      */
     8     public ArrayList(int initialCapacity) {
     9         if (initialCapacity > 0) {
    10             this.elementData = new Object[initialCapacity];
    11         } else if (initialCapacity == 0) {
    12             this.elementData = EMPTY_ELEMENTDATA;
    13         } else {
    14             throw new IllegalArgumentException("Illegal Capacity: "+
    15                                                initialCapacity);
    16         }
    17     }
    View Code

           解释:指定elementData数组的大小,不允许初始化容量小于0,否则抛出异常;

           (2) public ArrayList()

    1 /**
    2      * Constructs an empty list with an initial capacity of ten.
    3      */
    4     public ArrayList() {
    5         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    6     }
    View Code

           解释:当初始化容量大小没给定时,会将elementData赋值成空集合。

           (3) public ArrayList(Collection<? extends E> c)

     1  /**
     2      * Constructs a list containing the elements of the specified
     3      * collection, in the order they are returned by the collection's
     4      * iterator.
     5      *
     6      * @param c the collection whose elements are to be placed into this list
     7      * @throws NullPointerException if the specified collection is null
     8      */
     9     public ArrayList(Collection<? extends E> c) {
    10         elementData = c.toArray();
    11         if ((size = elementData.length) != 0) {
    12             // c.toArray might (incorrectly) not return Object[] (see 6260652)
    13             if (elementData.getClass() != Object[].class)
    14                 elementData = Arrays.copyOf(elementData, size, Object[].class);
    15         } else {
    16             // replace with empty array.
    17             this.elementData = EMPTY_ELEMENTDATA;
    18         }
    19     }
    View Code

           解释:当传递的参数为集合类型时,会把集合类型转化为数组类型,并赋值给elementData。

           note:上面代码有提到一个java的bug,即编号6260652。可以查看为什么c.toArray不一定返回Object[ ]. 倘若c.toArray()返回的数组类型不是Object[ ],则可以利用Arrays.copyOf()来创建一个大小为size的object[]数组。

           note:那么为什么会存在不一定返回object[]数组呢?看如下例子:

     1 public static void test()
     2     {
     3         Sub[] subArray = {new Sub(), new Sub()};
     4         System.out.println(subArray.getClass());
     5 
     6         // class [Lcollection.Sub;
     7         Base[] baseArray = subArray;  //向上转型
     8         System.out.println(baseArray.getClass());
     9 
    10         // java.lang.ArrayStoreException
    11         baseArray[0] = new Base();//会抛出异常,因为baseArray中实际存放的是Sub类型的,虽然Base数组创建成功,但是里面存放的并不是Base,而是Sub
    12     }
    View Code

           这个例子就说明了:假如我们有一个Object[]数组,并不代表着我们可以将Object对象存进去,这取决于数组中元素实际的类型。

           2.4 类的成员方法

           选取部分进行分析,即增删改查四个。

          (1)add

            有两个add方法,add(E e)和add(int index,E element)。前者是将指定的元素添加到ArrayList的最后位置,而后者是在指定的位置添加元素。

    1     public boolean add(E e) {
    2         // note: size + 1,保证资源空间不被浪费,在当前条件下,保证要存多少个元素,就只分配多少个空间资源
    3         ensureCapacityInternal(size + 1);  // Increments modCount!!
    4         elementData[size++] = e;
    5         return true;
    6     }
    View Code
     1  /**
     2      *在这个ArrayList中的指定位置插入指定的元素,
     3      *  - 在指定位置插入新元素,原先在 index 位置的值往后移动一位,将新的值插入到空缺出来的index地方去
     4      */
     5     public void add(int index, E element) {
     6         rangeCheckForAdd(index);//判断角标是否越界
     7         ensureCapacityInternal(size + 1);  // Increments modCount!!
     8         //第一个是要复制的数组,第二个是从要复制的数组的第几个开始,
     9         // 第三个是复制到哪里去,第四个是复制到的数组从第几个开始存放,最后一个是复制长度
    10         System.arraycopy(elementData, index, elementData, index + 1,
    11                 size - index);
    12         elementData[index] = element;
    13         size++;
    14     }
    View Code

     对比上面两个add方法,发现都出现了一个共同的函数ensureCapacityInternal,字面含义就是确保elementData有合适的容量大小。那么它的源码具体实现如下:

    1 private void ensureCapacityInternal(int minCapacity) {
    2         ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    3   }
    4 
    5 note:这是一个私有方法,只在本类使用。是为了保证空间资源不被浪费,尤其是add方法中更需要特别注意。
    View Code

    在ensureCapacityInternal方法中我们又发现了ensureExplicitCapacity方法,这个函数也是为了确保elemenData数组有合适的容量大小。ensureExplicitCapacity的源码如下:

    1  private void ensureExplicitCapacity(int minCapacity) {
    2         // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的
    3         modCount++;
    4         // 防止溢出代码:确保指定的最小容量 > 数组缓冲区当前的长度
    5         // overflow-conscious code
    6         if (minCapacity - elementData.length > 0)
    7             grow(minCapacity);
    8     }
    View Code

    fail--fast机制????溢出代码???,后面专门补充说明

    这个方法中又调用了grow方法,具体实现如下:

     1 /**
     2      * 私有方法:扩容,以确保 ArrayList 至少能存储 minCapacity 个元素
     3      * - 扩容计算:newCapacity = oldCapacity + (oldCapacity >> 1);  扩充当前容量的1.5倍
     4      * @param minCapacity    指定的最小容量
     5      */
     6     private void grow(int minCapacity) {
     7         // overflow-conscious code
     8         int oldCapacity = elementData.length;
     9         int newCapacity = oldCapacity + (oldCapacity >> 1);
    10         if (newCapacity - minCapacity < 0)
    11             newCapacity = minCapacity;
    12         if (newCapacity - MAX_ARRAY_SIZE > 0)
    13             newCapacity = hugeCapacity(minCapacity);
    14         // minCapacity is usually close to size, so this is a win:
    15         elementData = Arrays.copyOf(elementData, newCapacity);
    16     }
    View Code

    Note:真正的扩容操作还是在grow方法中实现的。

           因此,当添加一个对象的时候,不一定会进行扩容,比如初始化数组就足够大,用来存放要添加的元素;又或者必须扩容。因此总结成如下图形,来表示当一个add操作发生时,方法调用过程(note:红色箭头表示可能会发生的调用):

                                                                    

            举例:

    网上看到很好的两个例子,直接粘贴过来了。(源自:https://www.cnblogs.com/leesf456/p/5308358.html)

            1)例一:扩容

          List<Integer> lists = new ArrayList<Integer>();
          lists.add(8);

    说明:初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小

      说明:我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow,最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。

            2)例二:不扩容

       List<Integer> lists = new ArrayList<Integer>(6);
       lists.add(8);

    说明:调用的ArrayList(int)型构造函数,那么elementData被初始化为大小为6的Object数组,在调用add(8)方法时,具体的步骤如下:

      说明:我们可以知道,在调用add方法之前,elementData的大小已经为6,之后再进行传递,不会进行扩容处理。

          (2)remove

     1 public E remove(int index) {
     2         rangeCheck(index);
     3 
     4         modCount++;
     5         E oldValue = elementData(index);
     6 
     7         int numMoved = size - index - 1;
     8         if (numMoved > 0)
     9             System.arraycopy(elementData, index+1, elementData, index,
    10                              numMoved);
    11         elementData[--size] = null; // clear to let GC do its work
    12 
    13         return oldValue;
    14     }
    View Code
     1 public boolean remove(Object o) {
     2         if (o == null) {
     3             for (int index = 0; index < size; index++)
     4                 if (elementData[index] == null) {
     5                     fastRemove(index);
     6                     return true;
     7                 }
     8         } else {
     9             for (int index = 0; index < size; index++)
    10                 if (o.equals(elementData[index])) {
    11                     fastRemove(index);
    12                     return true;
    13                 }
    14         }
    15         return false;
    16     }
    View Code

           解释:该类中存在两个remove。前者是移除指定索引处的元素,此时会把指定索引到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了GC回收。后者是移除指定的元素。

          (3)set

    1 public E set(int index, E element) {
    2         rangeCheck(index);
    3 
    4         E oldValue = elementData(index);
    5         elementData[index] = element;
    6         return oldValue;
    7     }
    View Code

          解释:修改了指定索引处的值。

          (4)get

     1 public E get(int index) {
     2         rangeCheck(index);
     3 
     4         return elementData(index);
     5 }
     6 
     7 
     8 @SuppressWarnings("unchecked")
     9     E elementData(int index) {
    10         return (E) elementData[index];
    11     }
    12 
    13 
    14 elementData经过了由object向下转型成为了E
    View Code

    三. ArrayList的性能分析

    ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。

        3.1 为什么随机访问效率高

         通过get(int index)获取ArrayList第index个元素时。直接返回数组中index位置的元素,而不需要像LinkedList一样进行查找

        3.2 为什么随机插入、删除效率低

         通过上面的源码分析我们知道,add一个元素,将会调用ensureCapacity(size+1),它的作用是“确认ArrayList的容量,如果容量不够,就增加容量”。继续追究原来是System.arraycopy(elementData, index, elementData, index + 1, size - index)这一句存在真正的耗时操作,这会导致index之后的元素移动,会造成index止呕的所有元素的改变。同理,remove方法也是如此。因此效率低。

        3.3 当有大量数据需要添加时,怎么做提高效率

         如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。

        3.4 ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性

    Note:本文的部分内容来自:https://www.cnblogs.com/leesf456/p/5308358.html

     
  • 相关阅读:
    three.js
    three.js
    three.js
    反射API提供的常用类和函数
    PHP控制反转(IOC)和依赖注入(DI)
    优化思路以及优化过程
    nginx的缓存设置提高性能
    网页内容的压缩编码与传输速度优化
    nginx日志按日期自动切割脚本
    mysql数据备份
  • 原文地址:https://www.cnblogs.com/Hermioner/p/9742521.html
Copyright © 2020-2023  润新知