• JDK源码分析 – ArrayList


    ArrayList类的申明

    ArrayList是一个支持泛型的,底层通过数组实现的一个可以存任意类型的数据结构,源码中的定义如下:

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

    ArrayList类继承了AbstractList抽象类,AbstractList提供了List接口的默认实现

    ArrayList实现了以下几个接口:

    List<E>接口:约定List的操作规范,提供了一系列操作方法约定

    RandomAccess接口:该接口约定其实现类支持随机访问,即可以通过下标的方式访问其中的元素

    Cloneable接口:约定其实现类实例是可以被克隆的,通过调用Object.clone方法返回该对象的浅拷贝

    Serializable接口:约定其实现类实例可以被序列化和反序列化

    ArrayList主要字段、属性说明

        // 版本号
        private static final long serialVersionUID = 8683452581122892189L;
        // 缺省容量
        private static final int DEFAULT_CAPACITY = 10;
        // 空对象数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
        // 缺省空对象数组
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        // 元素数组
        transient Object[] elementData;
        // 实际元素大小,默认为0
        private int size;
        // 最大数组容量
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    其中有个重要的属性elementData其作用是存放集合元素,这说明了ArrayList内部其实是通过数组实现的。其修饰符transient 表明这个字字段在序列化时被忽略不序列化。

    ArrayList部分方法分析

    构造函数

    • 无参构造函数:初始化一个长度为0的空数组
    1 public ArrayList() {
    2     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    3 }
    4 
    5 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    • ArrayList(int) 构造函数:初始化一个指定长度的数组
     1 public ArrayList(int initialCapacity) {
     2     if (initialCapacity > 0) {
     3     //初始化一个容量为initialCapacity的数组
     4         this.elementData = new Object[initialCapacity];
     5     } else if (initialCapacity == 0) {
     6     //初始化空数组
     7         this.elementData = EMPTY_ELEMENTDATA;
     8     } else {
     9     //如果尝试初始化一个容量小于0的数组,则直接抛异常
    10         throw new IllegalArgumentException("Illegal Capacity: "+
    11                                            initialCapacity);
    12     }
    13 }
    • ArrayList(Collection<? extends E>)构造函数:初始化一个数组,并将参数集合中的元素复制到数组中
     1 public ArrayList(Collection<? extends E> c) {
     2     //将参数集合转化为数组,赋值到ArrayList内部存储属性上
     3     elementData = c.toArray();
     4     if ((size = elementData.length) != 0) {
     5         // c.toArray might (incorrectly) not return Object[] (see 6260652)
     6         //如果elementData数组类型不是Object[],则重新将elementData中元素转为Object复制到elementData中
     7         if (elementData.getClass() != Object[].class)
     8             elementData = Arrays.copyOf(elementData, size, Object[].class);
     9     } else {
    10         //为空则返回空数组
    11         this.elementData = EMPTY_ELEMENTDATA;
    12     }
    13 }

    上面的代码中为什么要再次判断?Collection类本身的toArray方法是返回Object[]类型数组,但是Java中如果子类如果继承Collection并重写了toArray方法,则返回的可能并不是Object[]类型数值,比如String[]等其他类型

    Add(E e)、add(int index, E element)、addAll(Collection<? extends E> c) 、addAll(int index, Collection<? extends E> c)

    ArrayList提供了这两个add操作方法,Add(E e)直接向素组末尾添加元素,add(int index, E element)向指定index索引处添加元素

     1 //直接向素组末尾添加元素
     2 public boolean add(E e) {
     3     //判断数组容量是否还可以添加,不够添加则扩充数组容量
     4     ensureCapacityInternal(size + 1);  // Increments modCount!!
     5     //将元素添加到数组末尾
     6     elementData[size++] = e;
     7     return true;
     8 }
     9 
    10 private void ensureCapacityInternal(int minCapacity) {
    11     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    12 }
    13 
    14 
    15 private void ensureExplicitCapacity(int minCapacity) {
    16     //用于迭代器
    17     modCount++;
    18 
    19     //期望的最小数组容量大于当前数组容量,则扩容
    20     if (minCapacity - elementData.length > 0)
    21         grow(minCapacity);
    22 }
    23 
    24 //计算期望最小的素组容量
    25 private static int calculateCapacity(Object[] elementData, int minCapacity) {
    26         // DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
    27     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {  
    28         // DEFAULT_CAPACITY=10,也就是说如果此时最小返回一个长度为10的数组
    29         return Math.max(DEFAULT_CAPACITY, minCapacity);
    30     }
    31     return minCapacity;
    32     }
    33 
    34 //扩容
    35 private void grow(int minCapacity) {
    36     //当前数组容量
    37 int oldCapacity = elementData.length;
    38   //计算新素组容量,为当前数组容量的1.5倍
    39     int newCapacity = oldCapacity + (oldCapacity >> 1);
    40   //判断新数组容量与期望数组容量大小,取值大的一方
    41     if (newCapacity - minCapacity < 0)
    42         newCapacity = minCapacity;
    43   // MAX_ARRAY_SIZE =Integer.MAX_VALUE – 8= 2147483639
    44   //如果新数组容量大于2147483639,则使用扩展到最大Integer.MAX_VALUE 
    45     if (newCapacity - MAX_ARRAY_SIZE > 0)
    46         newCapacity = hugeCapacity(minCapacity);
    47     //将原数组中的元素拷贝到新素组中
    48        elementData = Arrays.copyOf(elementData, newCapacity);
    49 }

    上面的代码注释已经写的很清楚了,add方法的逻辑是首选检查数组容量是否够用,如果容量不足,则进行扩容,扩容策略是如果原素组为空,则返回一个长度为10的数组,否则数组容量扩充到原素组的1.5倍,最终数组容量最大为Integer.Max_VALUE=2147483647

     1 //向指定索引处添加元素
     2 public void add(int index, E element) {
     3 //检查指定索引合法性
     4     rangeCheckForAdd(index);
     5 
     6     ensureCapacityInternal(size + 1);  // Increments modCount!!
     7     System.arraycopy(elementData, index, elementData, index + 1,
     8                      size - index);
     9     elementData[index] = element;
    10     size++;
    11 }

    add(int index, E element)内部多了一个验证指定索引合法性逻辑,其他与add(E element)实现逻辑基本一致。

     1 //添加集合
     2 public boolean addAll(Collection<? extends E> c) {
     3     Object[] a = c.toArray();
     4     int numNew = a.length;
     5     //检查是否需要扩容
     6     ensureCapacityInternal(size + numNew);  // Increments modCount
     7     System.arraycopy(a, 0, elementData, size, numNew);
     8     size += numNew;
     9     return numNew != 0;
    10 }
    11 public boolean addAll(int index, Collection<? extends E> c) {
    12     rangeCheckForAdd(index);
    13 
    14     Object[] a = c.toArray();
    15     int numNew = a.length;
    16     ensureCapacityInternal(size + numNew);  // Increments modCount
    17 
    18     int numMoved = size - index;
    19     if (numMoved > 0)
    20         System.arraycopy(elementData, index, elementData, index + numNew,
    21                          numMoved);
    22 
    23     System.arraycopy(a, 0, elementData, index, numNew);
    24     size += numNew;
    25     return numNew != 0;
    26 }

    addAll方法实现逻辑与add方法基本相同

    get(int index)

    get方法返回此列表中指定位置的元素,内部实现首先判断一下索引是否越界(居然没有判断小于0,实际上小于0时,数组读取也会抛异常),然后取出对应索引位置处的元素,另外由于ArrayList内部是用Object[]实现存储的,get(int index)返回泛型E,实际上elementData(index)内部实现将Object转为E

     1 public E get(int index) {
     2     //验证索引是否越界
     3     rangeCheck(index);
     4 
     5     return elementData(index);
     6 }
     7 
     8 private void rangeCheck(int index) {
     9     if (index >= size)
    10         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    11 }

    set(int index,E element)

    set方法的功能是将指定索引处的元素修改为element值,并返回该位置原来的值

    public E set(int index, E element) {
        //验证索引合法性
        rangeCheck(index);
          //读取原来的值
        E oldValue = elementData(index);
      //替换为目标值
        elementData[index] = element;
        return oldValue;
    }

    remove(int index)、remove(Object o)、removeAll(Collection<?> c)、removeIf(Predicate<? super E> filter)、removeRange(int fromIndex, int toIndex)

    ArrayList提供了一系列删除元素的方法,下面分析一个基础的remove(int index):

     1 //删除指定索引处的元素
     2 public E remove(int index) {
     3   //校验索引合法性
     4     rangeCheck(index);
     5 
     6     //删除操作影响数组列表结构,所以modCount自增1
     7     modCount++;
     8   //读取将要删除的元素
     9     E oldValue = elementData(index);
    10     //需要被移动的元素起始位置(该删除元素后面的元素都需要移动)
    11     int numMoved = size - index - 1;
    12   //存在移动的元素,则所有元素都往前移动一个位置
    13     if (numMoved > 0)
    14         System.arraycopy(elementData, index+1, elementData, index,
    15                          numMoved);
    16   //由于所有元素都向前移动了,最后一个空出来的位置设置为null
    17     elementData[--size] = null; // clear to let GC do its work
    18     //返回被删除的元素
    19     return oldValue;
    20 }

    IndexOf(Object o) 、lastIndexOf(Object o) 

    如果我们需要检查列表中某个元素的位置,则可以使用indexOf方法,此方法返回被检查元素在列表中第一次出现的下标,如果未找到该元素,则返回-1

     1 //查找指定元素的索引
     2 public int indexOf(Object o) {
     3   //顺序遍历数组,返回第一个出现位置的下标
     4     if (o == null) {
     5         for (int i = 0; i < size; i++)
     6             if (elementData[i]==null)
     7                 return i;
     8     } else {
     9         for (int i = 0; i < size; i++)
    10             if (o.equals(elementData[i]))
    11                 return i;
    12     }
    13   //不存在返回-1
    14     return -1;
    15 }

    lastIndexOf(Object o)返回指定元素在数组中最后一次出现的下标

    iterator()、listIterator()、listIterator(int index)

    • iterator()方法: 返回一个ArrayList中元素的迭代器,实现代码如下:
     1 public Iterator<E> iterator() {
     2     return new Itr();
     3 }
     4 
     5 private class Itr implements Iterator<E> {
     6 //下一个要返回元素的索引
     7     int cursor;       // index of next element to return
     8 //最后一个返回元素的索引
     9     int lastRet = -1; // index of last element returned; -1 if no such
    10     int expectedModCount = modCount;
    11 
    12     Itr() {}
    13 
    14 //判断是否还存在下一个元素
    15 public boolean hasNext() {
    16         return cursor != size;
    17     }
    18 
    19     @SuppressWarnings("unchecked")
    20     public E next() {
    21 //校验,在迭代器进行元素遍历期间如果修改数组长度,则抛出异常
    22         checkForComodification();
    23         int i = cursor;
    24         if (i >= size)
    25             throw new NoSuchElementException();
    26         Object[] elementData = ArrayList.this.elementData;
    27         if (i >= elementData.length)
    28             throw new ConcurrentModificationException();
    29 //指向下一个元素
    30         cursor = i + 1;
    31 //返回索引值为i处的元素,并将i赋值给lastRet:代表最后返回元素的索引
    32         return (E) elementData[lastRet = i];
    33     }
    34 
    35     //通过迭代器删除元素,不会抛异常
    36     public void remove() {
    37         if (lastRet < 0)
    38             throw new IllegalStateException();
    39         checkForComodification();
    40 
    41         try {
    42             ArrayList.this.remove(lastRet);
    43             cursor = lastRet;
    44             lastRet = -1;
    45             expectedModCount = modCount;
    46         } catch (IndexOutOfBoundsException ex) {
    47             throw new ConcurrentModificationException();
    48         }
    49     }
    50 
    51     @Override
    52     @SuppressWarnings("unchecked")
    53     public void forEachRemaining(Consumer<? super E> consumer) {
    54         Objects.requireNonNull(consumer);
    55         final int size = ArrayList.this.size;
    56         int i = cursor;
    57         if (i >= size) {
    58             return;
    59         }
    60         final Object[] elementData = ArrayList.this.elementData;
    61         if (i >= elementData.length) {
    62             throw new ConcurrentModificationException();
    63         }
    64         while (i != size && modCount == expectedModCount) {
    65             consumer.accept((E) elementData[i++]);
    66         }
    67         // update once at end of iteration to reduce heap write traffic
    68         cursor = i;
    69         lastRet = i - 1;
    70         checkForComodification();
    71     }
    72 
    73     final void checkForComodification() {
    74         if (modCount != expectedModCount)
    75             throw new ConcurrentModificationException();
    76     }
    77 }

    迭代器的实际应用:

      1. 使用迭代器iterator遍历:

    1     Iterator<Integer> list= array.iterator();
    2         while(list.hasNext()){
    3             //array.add(4);  add() 和remove()会导致modCount发生变化,从而导致迭代过程中抛出异常
    4             int value = it.next();
    5        //使用迭代器提供的remove()方法避免抛异常,原因:迭代器的remove方法在删除元素之后对将ArrayList的modCount覆盖了迭代器类的expectedModCount
    6             it.remove();
    7        
    8   }

      2.使用forEach遍历:反编译class文件可以发现其本质还是使用了iterator迭代器

      for(Integer item : array){
                //item.add()和item.remove()都将报错
                System.out.println(value);
            }
    • listIterator()方法:返回返回ArrayList元素的列表迭代器,与Iterator迭代器相比,它还提供了向前遍历,增加元素,修改元素的操作,其实现代码如下
     1 public ListIterator<E> listIterator() {
     2     return new ListItr(0);
     3 }
     4 
     5 private class ListItr extends Itr implements ListIterator<E> {
     6     ListItr(int index) {
     7         super();
     8     //初始化游标
     9         cursor = index;
    10     }
    11 
    12   //判断是否存在上 一个元素
    13     public boolean hasPrevious() {
    14         return cursor != 0;
    15     }
    16 
    17   //返回下一个将遍历的元素索引
    18     public int nextIndex() {
    19         return cursor;
    20     }
    21 
    22   //返回前一个元素索引
    23     public int previousIndex() {
    24         return cursor - 1;
    25     }
    26 
    27   //向前遍历:返回当前索引的上一个元素
    28     @SuppressWarnings("unchecked")
    29     public E previous() {
    30       //校验,在迭代器进行元素遍历期间如果修改数组长度,则抛出异常
    31         checkForComodification();
    32       //计算前一个元素索引
    33         int i = cursor - 1;
    34         if (i < 0)
    35             throw new NoSuchElementException();
    36         Object[] elementData = ArrayList.this.elementData;
    37         if (i >= elementData.length)
    38             throw new ConcurrentModificationException();
    39       //游标指向前一个元素
    40         cursor = i;
    41       //返回前一个元素
    42         return (E) elementData[lastRet = i];
    43     }
    44      
    45     //修改当前位置的元素
    46     public void set(E e) {
    47         if (lastRet < 0)
    48             throw new IllegalStateException();
    49         checkForComodification();
    50 
    51         try {
    52             ArrayList.this.set(lastRet, e);
    53         } catch (IndexOutOfBoundsException ex) {
    54             throw new ConcurrentModificationException();
    55         }
    56     }
    57   //当前位置新增元素
    58     public void add(E e) {
    59         checkForComodification();
    60 
    61         try {
    62             int i = cursor;
    63             ArrayList.this.add(i, e);
    64             cursor = i + 1;
    65             lastRet = -1;
    66             expectedModCount = modCount;
    67         } catch (IndexOutOfBoundsException ex) {
    68             throw new ConcurrentModificationException();
    69         }
    70     }
    71 }

    最后:ArrayList其实就是一个动态的Array,并且提供了一些便携的操作方法而已。

  • 相关阅读:
    Select2插件的隐藏、设置宽度
    远程登陆Linux服务器
    ECMAScript typeof用法
    Select2异步搜索数据
    ECMAScript 引用类型
    LeetCode 任务调度器-Python3<八>
    sort、sorted高级排序-Python3.7 And 算法<七>
    LeetCode算法笔记目录
    数据结构-Python3.7<三>
    【夯实PHP基础】php开发时遇到白页的调试方法
  • 原文地址:https://www.cnblogs.com/ashleyboy/p/9573340.html
Copyright © 2020-2023  润新知