美文网首页
ArrayList源码分析(一)

ArrayList源码分析(一)

作者: 一顆瓜子 | 来源:发表于2020-03-16 23:24 被阅读0次

    当你停下来休息的时候,不要忘记,别人还在奔跑~

    先抛出个问题:大家都知道ArrayList底层是由数组实现的,java中标准数组是定长的,在数组被创建之后,它们不能被加长或缩短。这就意味着在创建数组时需要知道数组的所需长度,为什么ArrayList可以动态扩容?

    1. ArrayList 的构造函数

    /**
         * 默认初始容量大小
         */
        private static final int DEFAULT_CAPACITY = 10;
        
    
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        /**
         *默认构造函数,构造一个空数组(无参数构造)
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
        
        /**
         * 带初始容量参数的构造函数。(用户自己指定容量)
         */
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {//初始容量大于0
                //创建initialCapacity大小的数组
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {//初始容量等于0
                //创建空数组
                this.elementData = EMPTY_ELEMENTDATA;
            } else {//初始容量小于0,抛出异常
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
    
       /**
        *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
        *如果指定的集合为null,throws NullPointerException。 
        */
         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;
            }
        }
    

    2.ArrayList 扩容机制

    List扩容实现步骤,总的来说就是分两步:

    • 扩容
      把原来的数组复制到另一个内存空间更大的数组中
    • 添加元素
      把新元素添加到扩容以后的数组中

    2.1 假设使用默认无参构造函数,即初始数组元素个数为0(size=0)

    /**
         *默认构造函数
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    2.2add方法

        /**
         * 将指定的元素追加到此列表的末尾。 
         */
        public boolean add(E e) {
             //添加元素之前,先调用ensureCapacityInternal方法
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //这里看到ArrayList添加元素的实质就相当于为数组赋值
            elementData[size++] = e;
            return true;
        }
    

    可以看到以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。

    2.3 可以看到 add 方法 首先调用了ensureCapacityInternal(size + 1)

         private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
          }
    
        //得到最小扩容量
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                // 获取默认的容量和传入参数的较大值           
               return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    

    当 要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。

    2.4 ensureExplicitCapacity()方法

    // 确保是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
          // 记录被修改的次数  
          modCount++;
    
            // 最小扩容单位长度大于数组长度,表示需要扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

    2.5 grow() 方法 (核心)

        /**
         * 要分配的最大数组大小
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        /**
         * ArrayList扩容的核心方法。
         */
        private void grow(int minCapacity) {
            // oldCapacity为旧容量,newCapacity为新容量
            int oldCapacity = elementData.length;
            //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
            //结果就是将新容量更新为旧容量的1.5倍,
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //若新容量还是小于最小需要容量,那么就把最小需要容量(10)当作数组的新容量,
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
           // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
           //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 将原数组copy到新数组中
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity为偶数就是1.5倍,否则是1.5倍左右)! 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.

    ">>"(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源

    2.6 .hugeCapacity() 方法

    private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            //对minCapacity和MAX_ARRAY_SIZE进行比较
            //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
            //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
            //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    

    总结

    • 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的数组),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为10。此时,minCapacity - elementData.length > 0 成立,所以会进入 grow(minCapacity) 方法。
    • 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
    • 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。
      直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。
    • 把新元素添加到扩容以后的数组中。

    ArrayList源码分析(二)

    LinkedList源码分析

    相关文章

      网友评论

          本文标题:ArrayList源码分析(一)

          本文链接:https://www.haomeiwen.com/subject/swgoehtx.html