• 【Java】- 源码解析——ArrayList


    一、ArrayList简介

      由于ArrayList底层是通过数组进行实现的,所以我们在说ArrayList之前我们先说下数组

      数组:

        优点:   a、有序  ---- > 存储的顺序位置和访问取出的顺序一致

            b、查询取值速度快  ---- >  根据索引可以直接查询定位索要的value值

        缺点:    a、数组长度定义后不可改变,即不可扩容

            b、数组由于是有序,所以在中间进行插入删除值时会很慢

      ArrayList:

        a、由于ArrayList底层时通过数组来实现的List类,ArrayList集合满足了数组的所有有点,同时改善了数据的部分缺点,比如可以自动扩容

        b、该类定义了一个Object[]的数组,来达到存储任何值的效果,并且类中通过capacity属性来记录数组的长度,若是在数组中添加数据,

      那么capacity就会自动增长来统计Object[]的数组长度

        c、若是该类有大量数据存储,可以在创建对象时传入capacity值,来定义集合的长度(内部Object[])数组的长度,从而建少扩容次数,减少

      重分配的次数,提高性能

        d、ArrayList 和 vector 提供了一模一样的方法,唯一的缺点就是 vector 类是线程安全的,而 ArrayList 是线程非安全的,所以效率来说

      ArrayList 比 Vector 更快

    二、ArrayList继承关系

    1 public class ArrayList<E> extends AbstractList<E>
    2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    3 
    4 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

      有代码可知:

        ArrayList 继承 AbstractList 继承 AbstractCollection

            实现 List    RandomAccess   Cloneable    Serializable

    三、源码讲解

      1、类属性讲解

     1 public class ArrayList<E> extends AbstractList<E>
     2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
     3 {
     4     // 版本号用于序列反序列化时版本匹配
     5     private static final long serialVersionUID = 8683452581122892189L;
     6     // 缺省容量
     7     private static final int DEFAULT_CAPACITY = 10;
     8     // 空对象数组
     9     private static final Object[] EMPTY_ELEMENTDATA = {};
    10     // 缺省空对象数组
    11     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    12     // 元素数组用例存储arraylist的value值
    13     transient Object[] elementData;
    14     // 实际元素大小,默认为0
    15     private int size;
    16     // 最大数组容量
    17     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    18 }

      2、构造方法

        2.1、无参构造方法

    1 /**
    2 * 无参构造方法, 同时为 elementData 进行初始化,类型为Object[],
    3 * 且数组长度为空,后面会进行赋默认值10
    4 */
    5 public ArrayList() {
    6     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    7 }

        2.2、自定义数组长度构造方法

     1 public ArrayList(int initialCapacity) {
     2     if (initialCapacity > 0) {  // 自定义容量大小>0时,将自定义容量作为数组初始化容量大小
     3         this.elementData = new Object[initialCapacity];
     4     } else if (initialCapacity == 0) {// 自定义容量大小=0时,通过空对象数组EMPTY_ELEMENTDATA来初始化数组
     5         this.elementData = EMPTY_ELEMENTDATA;
     6     } else {  // 自定义容量大小<0时, 抛出IllegalArgumentException不合法的参数异常
     7         throw new IllegalArgumentException("Illegal Capacity: "+
     8                                            initialCapacity);
     9     }
    10 }

         2.3、通过一个集合来调用构造函数

     1 public ArrayList(Collection<? extends E> c) { // 传参为继承Collection的数组
     2     // object[] toArray() 从第一个到最后一个返回数组来初始化elementData 转换为数组
     3     elementData = c.toArray();  
     4     if ((size = elementData.length) != 0) {// 若是elementData.length不为0且将elementData.length赋值给size
     5         // 通过反射判断若是elementData对象不是Object[].class的对象
     6         if (elementData.getClass() != Object[].class) 
     7         // 通过Arrays.copyOf来处理,返回新数组对象,且数组类型为Object[]
     8             elementData = Arrays.copyOf(elementData, size, Object[].class); 
     9     } else {
    10         this.elementData = EMPTY_ELEMENTDATA; // 通过空对象数组EMPTY_ELEMENTDATA来初始化数组
    11     }
    12 }

      总结:arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。

      2.4、提供的核心方法

        2.4.1、add()方法:

          add(E e) :将指定的元素追加到此列表的末尾。
          add(int index, E element) :在此列表中的指定位置插入指定的元素。
          addAll(Collection<? extends E> c) : 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
          addAll(int index, Collection<? extends E> c) : 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
        a、 add(E e) :将指定的元素追加到此列表的末尾。
    1 // 在末尾添加元素<E>类型元素
    2 public boolean add(E e) {
    3     ensureCapacityInternal(size + 1);  // 判断数组容量是否够用 size代表数组中的元素个数
    4     elementData[size++] = e; // 将原则添加在数组中,且size自增1
    5     return true; 
    6 }
        数组容量判定方法解析一
     1 private void ensureCapacityInternal(int minCapacity) { // minCapacity=size+1 size代表数组中的元素个数
     2     // 若是elementData为空:
     3         // 1、无参函数创建 new ArrayList()
     4         // 2、有参自定义值为0,调用构造函数 new ArrayList(0)
     5         // 3、传入集合,但集合为空,new ArrayList(new LinkList(0))
     6     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
     7         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 将 minCapacity  赋值为10 默认值
     8     }
     9     // 具体判定容量方法
    10     ensureExplicitCapacity(minCapacity);
    11 }
        数组容量判定方法解析二
    1 private void ensureExplicitCapacity(int minCapacity) {
    2     modCount++;
    3     // minCapacity 解析:(minCapacity代表elementData数组存储数组所需的最小容量)
    4         //若是数组初始化后第一次调用 由ensureCapacityInternal()方法中判断可知minCapacity赋值为10
    5         //而elementData.length=0
    6         //若是数组初始化后非第一次调用 由ensureCapacityInternal()方法中判断可知minCapacity = size+1
    7     if (minCapacity - elementData.length > 0)
    8         grow(minCapacity);
    9 }
        数组扩容方法解析
     1 private void grow(int minCapacity) {
     2     // 获取elementData数组原长度 放入oldCapacity变量中
     3     int oldCapacity = elementData.length; 
     4     // 扩容elementData.lenth*1.5倍后的值放入变量newCapacity中, oldCapacity >> 1 有位移相当于oldCapacity/2
     5     int newCapacity = oldCapacity + (oldCapacity >> 1); 
     6     if (newCapacity - minCapacity < 0) // 扩容后的容量值<最小所需容量值 适用于elementData为空数组时
     7         newCapacity = minCapacity; // 就直接用最小容量进行扩容
     8     //private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
     9     if (newCapacity - MAX_ARRAY_SIZE > 0) // 原数组扩容1.5倍后的值>最大数组容量
    10         newCapacity = hugeCapacity(minCapacity); // 将最大容量赋值给newCapacity
    11     elementData = Arrays.copyOf(elementData, newCapacity);// 通过newCapacity进行扩容
    12 }
        hugeCapacity()方法解析
    1 private static int hugeCapacity(int minCapacity) {
    2     if (minCapacity < 0) // overflow 抛出OutOfMemoryError堆内存溢出
    3         throw new OutOfMemoryError();
    4    // Integer.MAX_VALUE:2147483647   MAX_ARRAY_SIZE:2147483639
    5    // 若是minCapacity > MAX_ARRAY_SIZE则返回Integer.MAX_VALUE 否则返回MAX_ARRAY_SIZE
    6     return (minCapacity > MAX_ARRAY_SIZE) ?
    7         Integer.MAX_VALUE :
    8         MAX_ARRAY_SIZE;
    9 }

        Arrays.copyOf() 方法说明

     1     //方法传回的数组是新的数组对象,改变传回数组中的元素值,不会影响原来的数组。
     2     // copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值
     3     // copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度小于原数组的长度,则舍弃多出值
     4     public static void main(String[] args) {
     5     int[] arr1 = {1, 2, 3, 4, 5};
     6     int[] arr2 = Arrays.copyOf(arr1, 5);
     7     int[] arr3 = Arrays.copyOf(arr1, 10);
     8     int[] arr4 = Arrays.copyOf(arr1, 4);
     9     System.out.print("原数组为:");
    10     for(int i : arr1){
    11         System.out.print(i+" ");
    12    }
    13     System.out.print("
    ");
    14     System.out.print("等于原数组为:");
    15     for(int i : arr2){
    16         System.out.print(i+" ");
    17     }
    18     System.out.print("
    ");
    19     System.out.print("多于原数组为:");
    20     for(int i : arr3){
    21         System.out.print(i+" ");
    22     }
    23     System.out.print("
    ");
    24     System.out.print("少于原数组为:");
    25     for(int i : arr4){
    26         System.out.print(i+" ");
    27     }
    28 }

        b、add(int index, E element) :在此列表中的指定位置插入指定的元素。
    1 public void add(int index, E element) {
    2     rangeCheckForAdd(index);  //校验索引有效性
    3     ensureCapacityInternal(size + 1);  // 校验数组容量是否需要扩容  参考以上详细讲解
    4     System.arraycopy(elementData, index, elementData, index + 1,
    5                      size - index);
    6     elementData[index] = element;
    7     size++;
    8 }
        rangeCheckForAdd()方法分析
    1 public void add(int index, E element) {
    2     rangeCheckForAdd(index);  //校验索引有效性
    3     ensureCapacityInternal(size + 1);  // 校验数组容量是否需要扩容  参考以上详细讲解
    4     System.arraycopy(elementData, index, elementData, index + 1,
    5                      size - index);
    6     elementData[index] = element;
    7     size++;
    8 }
        rangeCheckForAdd()方法分析
    1 private void rangeCheckForAdd(int index) {
    2     //若是索引信息>数组的容量或着索引信息<0 则抛出异常IndexOutOfBoundsException数组越界异常
    3     if (index > size || index < 0)
    4         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    5 }

        2.4.2、删除方法:

          remove(int index) : 删除该列表中指定位置的元素。
          remove(Object o) : 从列表中删除指定元素的第一个出现(根据值来删除,且仅删除第一个出现的值)
          removeAll(Collection<?> c) : 从此列表中删除指定集合中包含的所有元素。
          removeIf(Predicate<? super E> filter) : 删除满足给定谓词的此集合的所有元素。
          removeRange(int fromIndex, int toIndex) :从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
          由于删除代码功能相似,仅挑选常用的进行说明
        a、remove(int index) : 删除该列表中指定位置的元素
     1 public E remove(int index) {
     2     rangeCheck(index); // 索引校验  当index索引值>size数组容量时 IndexOutOfBoundsException 抛出数组越界异常
     3     modCount++;
     4     E oldValue = elementData(index);   // 取出数组索引对应的value值
     5     int numMoved = size - index - 1;  // 计算需要数组往前移动的位数
     6     if (numMoved > 0)
     7         System.arraycopy(elementData, index+1, elementData, index,
     8                          numMoved);  // 此方法为System类静态方法且native修饰,作用将删除索引位置后的所有元素,往前移动一位
     9     elementData[--size] = null; // size数组真实容量-1 且 将数组最后一位置为null 等待GC回收器删除
    10     return oldValue; // 返回被remove指定索引的值
    11 }
        b、remove(Object o) : 从列表中删除指定元素的第一个出现(根据值来删除,且仅删除第一个出现的值)
     1 // 该方法不用解释,简单来说就是通过传参Object o 值来循环遍历数组,
     2 // 若是存在这个元素就将该元素的索引传给fastRemove(index)来进行删除
     3 // 通过此方法 remove(Object o)可以知道 ArrayList可以存储null值
     4 public boolean remove(Object o) {
     5     if (o == null) {
     6         for (int index = 0; index < size; index++)
     7             if (elementData[index] == null) {
     8                 fastRemove(index);
     9                 return true;
    10             }
    11     } else {
    12         for (int index = 0; index < size; index++)
    13             if (o.equals(elementData[index])) {
    14                 fastRemove(index);
    15                 return true;
    16             }
    17     }
    18     return false;
    19 }

        fastRemove(index)分析

    1 // 此方法不做过多介绍,和 remove(int index) 几乎相同,只不过remove(int index) 获取了index对应的元素
    2 private void fastRemove(int index) {
    3     modCount++;
    4     int numMoved = size - index - 1;
    5     if (numMoved > 0)
    6         System.arraycopy(elementData, index+1, elementData, index,
    7                          numMoved);
    8     elementData[--size] = null; // clear to let GC do its work
    9 }
        c、removeAll(Collection<?> c) : 从此列表中删除指定集合中包含的所有元素

    1 public boolean removeAll(Collection<?> c) {
    2     Objects.requireNonNull(c);  // 校验集合不能为null 否则抛出空指针异常
    3     return batchRemove(c, false);
    4 }
        batchRemove(c, false)解析

     1 private boolean batchRemove(Collection<?> c, boolean complement) {
     2     // complement 为false 则为removeAll()调用使用 为true 则为retainAll()用 
     3     // retainAll() 是用来检测两个集合是否有交集的
     4     final Object[] elementData = this.elementData;   //将原集合,记名为 elementData
     5     int r = 0, w = 0; //r用来控制循环,w是记录有多少个交集/差集  removeAll 调用记录差集  retainAll()记录交集
     6     boolean modified = false;
     7     try {
     8         for (; r < size; r++)
     9             if (c.contains(elementData[r]) == complement) // 校验c集合元素是否在elementData中是否为false/true
    10                 // 若是 complement = true 则将 c中包含elementData中的元素 替换 elementData[w++]位置值 达到元素前移效果
    11                 // 若是 complement = false则将 c中不包含elementData中的元素 存入 elementData[w++]位置值 达到元素前移效果
    12                 elementData[w++] = elementData[r]; 
    13     } finally {
    14          if (r != size) { //  r != size 只会在 c.contains()直接报错,否则r==size 一直不走此段逻辑
    15             System.arraycopy(elementData, r,
    16                              elementData, w,
    17                              size - r);
    18             w += size - r;
    19         }
    20         if (w != size) { // 将 自 elementData[w] 到 elementData[size-1]的值全部置为null
    21             for (int i = w; i < size; i++)
    22                 elementData[i] = null;
    23             modCount += size - w; // 标记elementData数组修改次数
    24             size = w; // 修改 size 实例容量
    25             modified = true;
    26         }
    27     }
    28     return modified;
    29 }

        2.4.3、clear方法

     

          从列表中删除所有元素。

    1 // 循环遍历数组将数组元素全部置为null 等等GC回收机制回收
    2 public void clear() {
    3     modCount++;
    4     // clear to let GC do its work
    5     for (int i = 0; i < size; i++)
    6         elementData[i] = null;
    7 
    8     size = 0;
    9 }

        2.4.4、ensureCapacity(int minCapacity)方法

          增加此 ArrayList实例的容量,以确保它可以至少保存最小容量参数指定的元素数。
    public void ensureCapacity(int minCapacity) {
        // 如果elementData不为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(常量{})那么minExpand =0否则minExpand = DEFAULT_CAPACITY(常量10)
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0
            : DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            // 修改elementData容量
            ensureExplicitCapacity(minCapacity);
        }
    }

        ensureExplicitCapacity(minCapacity)解析

    1 private void ensureExplicitCapacity(int minCapacity) {
    2     modCount++;
    3     if (minCapacity - elementData.length > 0) // 若是 minCapacity>当前数组的容量 则进行扩容
    4         grow(minCapacity);
    5 }

          用指定的元素替换此列表中指定位置的元素

     1 public E set(int index, E element) {
     2     // 检验索引是否合法
     3     rangeCheck(index);
     4     // 旧值
     5     E oldValue = elementData(index);
     6     // 赋新值
     7     elementData[index] = element;
     8     // 返回旧值
     9     return oldValue;
    10 }

    四、ArrayList 总结:

       1、arrayList可以存放null。
      2、arrayList本质上就是一个elementData数组。
      3、arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
      4、arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全部删除集合中的元素。
      5、arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
      6、arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
     

     感谢!!!!

  • 相关阅读:
    VS 2010下一次性配置opencv(32位和64位相同)
    模拟鼠标事件
    Main函数参数argc,argv说明
    Visual Studio 2010 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏解决方案
    常量指针和指针常量
    strlen函数实现的几种方法
    杀死指定的进程名
    typedef和typename关键字
    如何理解dart的mixin
    c# 通过dllimport 调用c 动态链接库
  • 原文地址:https://www.cnblogs.com/tar8087/p/14393543.html
Copyright © 2020-2023  润新知