zoukankan      html  css  js  c++  java
  • new ArrayList(0) 和 new ArrayList() 和一样吗?

    第一感觉是一样的,盲猜后者调用了前者,并传入参数 0。然而,无论是 JDK 7 还是 JDK 8,这两个方法构造的结果都是不一样的。JDK 开发人员在这方面作了优化。

    JDK 7

    在 Java 7 中,这两个方法非常简答,ArrayList(int initialCapacity) 初始化动态数组的长度为指定的 initialCapacity,而 ArrayList() 调用了 ArrayList(int) ,传入参数 10,初始化了一个长度为 10 的数组。在 Java 7 中,后者确实调用了调用了前者,但是传入的参数并非 0,而是 10。

        public ArrayList(int initialCapacity) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            this.elementData = new Object[initialCapacity];
        }
    
        public ArrayList() {
            this(10);
        }
    

    那么,为什么初始化为 10,而不是初始化为 0 呢?初始化为 0 意味着一插入元素就会触发动态数组的扩容,如果逐个增加元素,容量变化为:0, 1, 2, 3, 4, 6, 9, 13, 19, 28 ...,公式为:newCapacity = oldCapacity + (oldCapacity / 2)。意味着如果初始化为 0,连续插入少量元素会频繁触发扩容,而 ArrayList 中插入少量元素是大概率的。所以干脆就直接初始化为 10,减少扩容的开销。

    但是这样存在一个问题:无论是否使用 ArrayList 实例,只要调用了 new ArrayList(),就会初始化一个长度为 10 的 Object 数组,不管未来是否使用这个实例。JDK 7 的更新版本中和 JDK 8 中采用延迟初始化的策略解决了这个问题。

    JDK 8

    JDK 开发人员发现大约 80% 的时候 Java 程序员调用 new ArrayList() 来实例化 ArrayList 对象(来源:RFR JDK-7143928)。因此,延迟初始化策略能够优化大多数使用 ArrayList 的情况。

    代码:

        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);
            }
        }
    
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    代码中使用了 EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 两个变量来区分 new ArrayList(0) 和 new ArrayList() 的情况,这两个成员变量默认值都是空的 Object 数组。在首次扩容的时候,根据 elementData 引用对象的不同来决定最小容量。

        // 供用户使用的 public 确保容量足够的方法
        public void ensureCapacity(int minCapacity) {
            int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 不是通过 new ArrayLsit() 初始化的
                // any size if not default element table
                ? 0
                // larger than default for default empty table. It's already
                // supposed to be at default size.
                : DEFAULT_CAPACITY; // 常量,值为 10
    
            if (minCapacity > minExpand) {
                ensureExplicitCapacity(minCapacity);
            }
        }
    
        // 供 ArrayList 内部使用的 private 确保容量足够的方法,与上面的方法平行
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 通过 new ArrayList() 来初始化的,最小取 DEFAULT_CAPACITY (值为 10)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    

    ArrayList 实例插入第 1 个元素时,如果实例是通过 new ArrayList(0) 来初始化的,计算出来的 minCapacity 为 0;如果是 new ArrayList() 构造出来的,计算出来的 minCapacity 为 DEFAULT_CAPACITY (值为 10)。然后将 minCapacity 传给 grow 方法,对数组进行扩容。

        // 扩容规则
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            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);
        }
    

    可以用如下图来表示从构造对象到插入第一个元素时 elementData 所引用对象的变化。

    list1开始new ArrayList(0)elementDataEMPTY_ELEMENTDATAlist1list1.add(1)elementData1list2开始new ArrayList()elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATAlist2list2.add(1)elementData1nullnullnullnullnullnullnullnullnull

    容量变化序列:

    构造方式 容量序列
    new ArrayList(0) 0, 1, 2, 3, 4, 6, 9, 13, 19, 28, 42, 63, ..., Integer.MAX_VALUE - 8
    new ArrayList() 0, 10, 15, 22, 33, 49, 73, 109, 163, 244, 366, ..., Integer.MAX_VALUE-8

    MAX_ARRAY_SIZE 的值为 Integer.MAX_VALUE - 8

    小结

    在构造 ArrayList 实例的时候,如果没有指定动态数组的初始容量,JDK 会自动给动态数组初始化一个容量,以减少插入少量元素时动态数组的扩容开销。但这样无论是否使用实例,都会去创建一个长度为 10 的 Object 数组。开发人员发现,绝大多数情况下代码都是直接通过 new ArrayList() 来构造实例的,于是在新版本的 JDK 中使用延迟初始化的策略优化了这些情况。

    在实际开发中,如果已知了 ArrayList 中需要存放的元素的数量,应该调用 new ArrayList(int initialCapacity) 方法来创建对象,这样可以消除动态数组扩容的开销,将增加元素的时间复杂度固定为 O(1)。如果存放数量未知,则调用 new ArrayList() 来创建对象,代码中已经通过设置初始容量、延迟初始化的方式使效率尽可能高。

  • 相关阅读:
    Linux部署thinkphp5,nginx服务器一直访问index/index问题解决方法
    如何防止XSS攻击?
    centos7+nginx + php 部署多站点
    dubbo+spring+mybatis分布式框架的使用
    增删改查sql语句
    (转)linux内核参数注释与优化
    php+nginx环境配置注意事项
    vs installer 将.net framework 集成到安装包中
    Method not found: 'System.Data.Entity.ModelConfiguration.Configuration.XXX
    未能加载文件或程序集“XXX”或它的某一个依赖项。
  • 原文地址:https://www.cnblogs.com/robothy/p/13970321.html
Copyright © 2011-2022 走看看