第一感觉是一样的,盲猜后者调用了前者,并传入参数 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. AcWing 380. 舞动的夜晚

    大型补档计划 题目链接 这题是求必须边,而不是不可行边,因为不可行边 = 必须边 + 死掉了的边(貌似lyd第三版书上还是说的不可行边)先跑最大流. 在跑完以后的残余网络上,对于一条当前匹配的边 \( ...

  2. 基于Fisco-Bcos的区块链智能合约-业务数据上链SDK实现

    合约的编写 基于springboot : https://github.com/FISCO-BCOS/spring-boot-starter pragma solidity ^0.4.24; cont ...

  3. uniapp中使用picker中的注意事项

    APP端中picker点击后不弹出: 1.请确保picker标签里面嵌套了一个view,并且view里面有值 2.请确保picker中的默认值的格式跟该picker类型的值对应 例如下面: <v ...

  4. django添加检查用户名和手机号数量接口

    1.1 在user/urls.py中添加 urlpatterns = [ path('count/', views.RegCountView.as_view()), # 查询用户名手机号使用量的视图, ...

  5. centos 7.0下安装MySQL 5.7.26

    1.下载MySQL 5.7.26安装包,卸载自带MySQL数据库 yum remove mariadb-libs -y yum install -y libaio-devel 2.上传MySQL 5. ...

  6. 前端使用canvas生成盲水印的加密解密

    为了保障信息安全,防止重大信息泄露,并且能够锁定泄露用户,需要对页面展示的图片加入当前用户信息的盲水印,即最终图片外观看起来和原图一样,但是经过解码以后可以识别出水印信息,并且在截图后仍能进行较好的识 ...

  7. Docker修改默认的网段

    一,问题 docker安装后默认的网段是172.17网段的,和真实环境网段冲突导致本机电脑无法连接docker机器. 二,解决办法 修改docker默认网段 1,先把docker停止 systemct ...

  8. java.net.BindException:Problem binding to [hostname:8088]地址已在使用

    异常提示端口号被占用 查找被占用的端口 netstat -tln netstat -tln | grep 8083 netstat -tln ## 查看端口使用情况,而netstat -tln | g ...

  9. ASP.NET Core 中间件的使用(二):依赖注入的使用

    写在前面 上一篇大家已经粗略接触了解到.NET Core中间件的使用:ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件, .NET Core框架中很多核心对象都是通过依 ...

  10. Promise 之初探

    陈旧的知识应该更新一下了,先尝试一下 Promise ,主要参考 https://www.cnblogs.com/whybxy/p/7645578.html 定义一个函数 直接上干货,定义一个函数: ...