• ArrayList源码阅读笔记


    1. 基础知识

    1.1 概念

    1. ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
    2. 该类封装了一个动态再分配的Object[]数组,每个对象都有一个capacity属性,表示它们所封装的Object[]数组长度,当向ArrayList中添加元素时,该属性会自动增加。
      如果向ArrayList中添加大量元素,可使用ensureCapacity()方法一次性增加capacity,可以减少增加重分配的次数提高性能。
    • ArrayList的用法和Vector相似,但Vector是一个较老的集合,具有很多缺点,不建议使用。
      ArrayList和Vector的区别是:ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector是线程安全的。可以使用CopyOnWriteArrayList。

    1.2 继承关系

    在这里插入图片描述
    由上图可知最根部就是实现了Collection接口,下面我们从ArrayList的角度来分析一下继承关系:
    在这里插入图片描述
    上面这张图可以很清晰的看出ArrayList实现了四个接口一个抽象类:继承AbstractList抽象类、实现了List、RandomAccess、Cloneable、Serializable接口。

    • 继承AbstractList, 实现了List。它是一个数组,提供了相关的增、删、修改、遍历等功能。
    • RandomAccess接口:这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如ArrayList。
    • Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。
    • Serializable接口:实现该序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类。

    1.3 特点

    1. ArrayList底层是一个动态扩容的数组结构,初始容量为10,每次容量不够的时候,扩容需要增加1.5倍的容量(当通过addAll()方法添加数据时有可能不是1.5被扩容);
    2. ArrayList允许存放重复数据,存储顺序按照元素的添加顺序,也允许Null;
    3. 底层使用Arrays.copyOf()函数进行扩容,每次扩容都会产生新的数组,和数组中内容的拷贝,会耗费性能,所以在多增删的操作情况可优先考虑LinkedList,如果多按下标存取元素情况下推荐使用ArrayList。
    4. ArrayList并不是一个线程安全的集合。如果集合的增删操作需要保证线程的安全性,可以考虑使用CopyOnWriteArrayList或者使用Collections.synchronizedList(List)函数返回一个线程安全的ArrayList类。

    2. 源码阅读

    2.1 类中的属性

       /**
       * 序列化时使用
       */
       private static final long serialVersionUID = 8683452581122892189L;
    
        /**
         * 集合的默认大小
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * 用于空实例的共享空数组实例
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        /**
        * 这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
        */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        /**
         * 存储ArrayList的元素的数组缓冲区。 ArrayList的容量是此数组缓冲区的长度。 
         * 添加第一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都将扩展为DEFAULT_CAPACITY。
         */
       transient Object[] elementData; // non-private to simplify nested class access
    
        /**
         * 当前ArrayList大小
         */
        private int size;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA的区分会在本文后续具体说明。

    2.2 构造方法

    ArrayList有三个构造方法

    在这里插入图片描述

    1. 无参构造方法

      /**
        * Constructs an empty list with an initial capacity of ten. 这里就说明了默认会给10的大小,所以说一开始ArrayList的容量是10
        */
       public ArrayList() {
           this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      ArrayList中存储的数据其实就是一个数组,这个数组就是elementDatam。此时初始容量是0。

    2. 有参数构造方法

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

      初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于0时,抛出异常。

    3. 参数为集合的构造方法

        public ArrayList(Collection<? extends E> c) {
           elementData = c.toArray();
           if ((size = elementData.length) != 0) {
               // c.toArray might (incorrectly) not return Object[] (see 6260652)
               if (elementData.getClass() != Object[].class)
                   elementData = Arrays.copyOf(elementData, size, Object[].class);
           } else {
               // replace with empty array.
               this.elementData = EMPTY_ELEMENTDATA;
           }
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      这是将已有的集合复制到 ArrayList 集合中去。
      可以看到,不管调用哪个构造方法,都会初始化内部elementData。

    2.3 添加元素

    通过前面的字段属性和构造函数,我们知道 ArrayList 集合是由数组构成的,那么向 ArrayList 中添加元素,也就是向数组赋值。我们知道一个数组的声明是能确定大小的,而使用 ArrayList 时,好像是能添加任意多个元素,这就涉及到数组的扩容。

    2.3.1 add()方法
      /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return true (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    
      /**
         * Inserts the specified element at the specified position in this
         * list. Shifts the element currently at that position (if any) and
         * any subsequent elements to the right (adds one to their indices).
         *
         * @param index index at which the specified element is to be inserted
         * @param element element to be inserted
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public void add(int index, E element) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    可以看到,不管是调用哪个构造方法,都会首先执行 ensureCapacityInternal(size + 1) 确认内部容量。

    2.3.2 ensureCapacityInternal()
        private void ensureCapacityInternal(int minCapacity) {
            // 如果创建ArrayList时,使用的无参的构造方法,那么就取默认容量10和最小需要容量中的较大一个值
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // 如果minCapacity比当前容量大,就执行grow扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        /**
         * The maximum size of array to allocate.
         * Some VMs reserve some header words in an array.
         * Attempts to allocate larger arrays may result in
         * OutOfMemoryError: Requested array size exceeds VM limit
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // 拿到当前容量
            int oldCapacity = elementData.length;
            // oldCapacity >> 1 意思是oldCapacity/2,所以新容量就是增加1/2
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //如果心容量小于需要的最小扩容的容量,以需要的最小容量为准进行扩容
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            // 如果新容量大于允许的最大容量,则以Interger的最大值进行扩容
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        // 设置容器容量为最大容量
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    上面代码注释很清楚,不赘述。
    此外ArrayList还有addAll(Collection c)、 addAll(int index, Collection c) 方法,类似。

    2.4 移除元素——remove ()方法

         /**
         * Removes the element at the specified position in this list.
         * Shifts any subsequent elements to the left (subtracts one from their
         * indices).
         *
         * @param index the index of the element to be removed
         * @return the element that was removed from the list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E remove(int index) {
            // 判断是否越界 不过为啥不判断<0的情况呢
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
            modCount++;
            E oldValue = (E) elementData[index];
            // 主要移动的元素数量,即 总长度-(下标 + 1)
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    
    
    
        /**
         * Removes the first occurrence of the specified element from this list,
         * if it is present.  If the list does not contain the element, it is
         * unchanged.  More formally, removes the element with the lowest index
         * i such that
         * (o==null ? get(i)==null : o.equals(get(i))) 
         * (if such an element exists).  Returns true if this list
         * contained the specified element (or equivalently, if this list
         * changed as a result of the call).
         *
         * @param o element to be removed from this list, if present
         * @return true if this list contained the specified element
         */
        public boolean remove(Object o) {
            if (o == null) {
                // 如果删除null数据,只会删除第一个null元素
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    
        /*
         * Private remove method that skips bounds checking and does not
         * return the value removed.
         */
        private void fastRemove(int index) {
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    2.4 设置元素 —— set()方法

    set()方法就是在指定位置改变一个元素的值

       /**
         * Replaces the element at the specified position in this list with
         * the specified element.
         *
         * @param index index of the element to replace
         * @param element element to be stored at the specified position
         * @return the element previously at the specified position
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E set(int index, E element) {
            //依然先判断是否越界,如果越界就抛出异常,你没有越界直接修改值,把旧值返回
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
            E oldValue = (E) elementData[index];
            elementData[index] = element;
            return oldValue;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.5 其它方法

    
        /**
         * Returns the element at the specified position in this list.
         *
         * @param  index index of the element to return
         * @return the element at the specified position in this list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
         public E get(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
            return (E) elementData[index];
        }
    
    
        /**
         * Removes all of the elements from this list.  The list will
         * be empty after this call returns.
         */
         public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }
    
    
        /**
         * Returns the index of the first occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the lowest index i such that
         * (o==null ? get(i)==null : o.equals(get(i))),
         * or -1 if there is no such index.
         */
        public int indexOf(Object o) {
            if (o == null) {
                for (int i = 0; i < size; i++)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    
        /**
         * Returns the index of the last occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the highest index i such that
         * (o==null ? get(i)==null : o.equals(get(i))),
         * or -1 if there is no such index.
         */
        public int lastIndexOf(Object o) {
            if (o == null) {
                for (int i = size-1; i >= 0; i--)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = size-1; i >= 0; i--)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    3. 总结

    1. ArrayList底层是一个动态扩容的数组,初始容量为10,每次容量不足时,扩容值当前的1.5倍容量。
    2. 增加(add)和删除(remove)操作会改变modCount,但是查找(get)和修改(set)不会修改。
    3. 从上面可以看出,增删都可能涉及数组拷贝,效率比较低,但是查找和修改时效率很高。
    4. 从上面可以看出,ArrayList对null元素是支持的,并且不会限制数量,也不会限制重复元素。
    5. 全文没见Synchronized关键字,也没有其它保证线程安全的操作,所以是线程不安全的,可以使用CopyOnWriteArrayList或者使Collections.synchronizedList(List)函数返回一个线程安全的ArrayList类来保证线程安全。
  • 相关阅读:
    k8s-list-watch集群资源调度
    Web自动化处理“滑动验证码”
    代码审计-2 SQL注入
    Spring Cache和Mysql主从复制
    浅谈如何使用 github.com/kardianos/service
    机器学习之查准率、查全率与F1
    STM32CubeMX教程18 DAC - DMA输出自定义波形
    2002NOIP普及组真题 4. 过河卒
    UG\NX二次开发 获取所有子部件,封装两个函数
    【UE5 C++基础 04】UHT基础
  • 原文地址:https://blog.csdn.net/xyzso1z/article/details/110287924