第一感觉是一样的,盲猜后者调用了前者,并传入参数 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 所引用对象的变化。

.st3 {fill:#191919;font-family:Consolas;font-size:6pt}
.st2 {fill:#191919;font-family:Consolas;font-size:8pt}
.st1 {fill:#303030;font-family:Consolas;font-size:8pt}
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() 来创建对象,代码中已经通过设置初始容量、延迟初始化的方式使效率尽可能高。

new ArrayList(0) 和 new ArrayList() 和一样吗?的更多相关文章

  1. javase基础回顾(一)ArrayList深入解析 解读ArrayList源代码(JDK1.8.0_92)

    我们在学习这一块内容时需要注意的一个问题是 集合中存放的依然是对象的引用而不是对象本身. List接口扩展了Collection并声明存储一系列元素的类集的特性.使用一个基于零的下标,元素可以通过它们 ...

  2. 比较List和ArrayList的性能及ArrayList和LinkedList优缺点

    List和ArrayList的性能比较 在使用ArrayList这样的非泛型集合的过程中,要进行装箱和拆箱操作,会有比较大的性能损失,而使用泛型集合就没有这样的问题.List是泛型,而ArrayLis ...

  3. 记录一下显示Map<String, ArrayList<String>>中的ArrayList里的数据的操作

    1.有以下数据: ArrayList<Employee> emp = new ArrayList<>(); emp.add(new Employee("zhang&q ...

  4. 为什么是List list = new ArrayList() 而不直接用ArrayList

    为什么是List list = new ArrayList(),而不直接用ArrayList? 编程是要面向对象编程,针对抽象(接口),而非具体.List 是接口,ArrayList是实现. 实现Li ...

  5. ArrayList深度分析:ArrayList和数组间的相互转换

    一.ArrayList转换为数组ArrayList提供public <T> T[] toArray(T[] a)方法返回一个按照正确的顺序包含此列表中所有元素的数组,返回数组的运行时类型就 ...

  6. 2.请介绍一下List和ArrayList的区别,ArrayList和HashSet区别

    第一问: List是接口,ArrayList实现了List接口. 第二问: ArrayList实现了List接口,HashSet实现了Set接口,List和Set都是继承Collection接口. A ...

  7. 5.秋招复习简单整理之请介绍一下List和ArrayList的区别,arrayList和HashSet区别?

    第一问:List是接口,ArrayList是List的实现类. 第二问:ArrayList是List的实现类,HashSet是Set的实现类,List和Set都实现了Collection接口. Arr ...

  8. 解析C#中[],List,Array,ArrayList的区别及应用

    [] 是针对特定类型.固定长度的. List 是针对特定类型.任意长度的. Array 是针对任意类型.固定长度的. ArrayList 是针对任意类型.任意长度的. Array 和 ArrayLis ...

  9. ArrayList——源码探究

    摘要 ArrayList 是Java中常用的一个集合类,其继承自AbstractList并实现了List 构造器 ArrayList 提供了三个构造器,分别是 含参构造器-1 // 含参构造器-1 / ...

随机推荐

  1. Raft概述

    Raft 1. 概述 Raft是一种一致性(共识)算法,相比Paxos,Raft更容易理解和实现,它将分布式一致性问题分解成多个子问题,Leader选举(Leader election).日志复制(L ...

  2. ACM训练赛:第20次

    这次的题思维都很强,等之后的考试结束会集中精力重新训练一些思维题. A - A simple question CodeForces - 520B 思路: 直接看的话,很容易发现如果 \(n > ...

  3. ubuntu18.04 登录界面循环,已解决

    按照百度的方法,要卸载重装nvidia,遇到如下问题 1.进入Ubuntu字符界面,出现乱码,猜测是sudo命令出现问题,果然 解决方案:$ PATH=/usr/kerberos/sbin:/usr/ ...

  4. Java为什么称为动态编译?

    Java在程序运行时产生Java类并编译成.class文件.

  5. 定制iview选择器——全选按钮

    源码下载:https://github.com/littleOneYuan/c_select 本文索引 效果 template data prop methods watch created 效果 下 ...

  6. 解决IDEA Maven下载依赖包速度慢问题

    右键项目,maven选项,"Open setting.xml"或"Create setting.xml",在 mirrors 节点添加下面代码. <mir ...

  7. Java中字段赋值顺序的问题

    static字段 public class Client { public static int i = 2; static { i = 100; } public static void main( ...

  8. Loading class `com.mysql.jdbc.Driver'. This is deprecated警告处理

    com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的区别 mysql客户端6以后,数据库驱动com.mysql.jdbc.Driver'已经被弃用了.应当 ...

  9. 统计文件行数,推荐使用LineNumberReader

    一.主题: 读取文本文件最大行数性能比较:lineNumberReader > Files.lines 二.code 1 @Test 2 public void testLineReader() ...

  10. 网络编程-python实现-socket(1.1.1)

    @ 目录 1.不同电脑进程之间如何通信 2.什么是socket 3.创建socket 1.不同电脑进程之间如何通信 利用ip地址 协议 端口 标识网络的进程,网络中的进程通信就可以利用这个标志与其他进 ...