一梦三千年

导航

【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("\n");
14     System.out.print("等于原数组为:");
15     for(int i : arr2){
16         System.out.print(i+" ");
17     }
18     System.out.print("\n");
19     System.out.print("多于原数组为:");
20     for(int i : arr3){
21         System.out.print(i+" ");
22     }
23     System.out.print("\n");
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循环。
 

 感谢!!!!

posted on 2021-02-10 02:44  一梦三千年  阅读(91)  评论(0编辑  收藏  举报