ArrayList源码中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别
2018年7月22日09:54:17
JDK 1.8.0_162 ArrayList源码中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别
写在前面的话:
关于阅读源码:刚开始学习的时候,觉得阅读源码是多么遥远的事情,但是不知不觉已经毕业一年了,自己的进步不多。华罗庚说,“自学,不怕起点低,就怕不到底”。阅读源码应该是比较“底”了吧,哈哈。阅读源码,在面试官问你这个问题:“你读过Java源码吗”的时候,你可以拍着胸口回答他:“读过!!!”。Last but not least,就是可以装逼:我已经读过Java源码了。(虽然不知道自己收获了多少)
言归正传,《Effective Java》第二版第47条:了解和使用类库中有这么一句话:每个程序员都应该熟悉java.lang、java.util,某种程度上还有java.io中的内容。然后我就从java.util开始读了。
本文只是讨论JDK 1.8.0_162中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别,关于源码详细解读请Google。
在ArrayList中有关EMPTY_ELEMENTDATA(下文用EE代替)和DEFAULTCAPACITY_EMPTY_ELEMENTDATA(下文用DEE代替)的声明定义如下:
/**
* Shared empty array instance used for empty instances.
* 用于ArrayList空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 用于默认大小空实例的共享空数组实例。我们将this(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
* 和EMPTY_ELEMENTDATA区别开来,以便在添加第一个元素时知道要膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这两个类常量EE和DEE都是表示空数组,只是名字不一样而已。
三个构造函数:
/**
* 有参
*/
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;
}
/**
* 参数为集合
*/
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;
}
}
其中无参构造器创建的实例al的elementData是DEE,有参构造函数创建的空实例al1和al2的elementData是EE。即:
// elementData = DEE
ArrayList<String> al = new ArrayList<String>();
// elementData = EE
ArrayList<String> al1 = new ArrayList<String>(0);
ArrarList<String> al2 = new ArrayList<String>(al1)
接下来看看add(E e)方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 当第一次调用add(E e)方法的时候,判读是不是无参构造函数创建的对象,如果是,
// 将DEFAULT_CAPACITY即10作为ArrayList的容量,此时minCapacity = 1
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
其他add方法如:add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)中都有ensureCapacityInternal(int minCapacity)方法,确保无参构成函数创建的实例al在添加第一个元素时,最小的容量是默认大小10。那有参构造函数创建的空实例al1、al2在通过add(E e)添加元素的时候是怎么样的呢?al1、al2容量增长是这样子的:0->1->2->3->4->6->9->13...,这样的增长是很慢的。具体扩容方式:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新容量为旧容量的1.5倍
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);
}
问题:两个类常量都是表示空数组,为什么要用两个呢?在Java7中只有一个类常量表示空数组,就是EE。Java8中添加了DEE代替了EE。
在Java7中ArrayList的构造函数:
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
完全就是DEE代替了EE。那EE干什么去了,看一下构造函数中EE安排在哪里了?都是在判断容量为空的情况下,赋值给elementData。Java7中如果容量是0的话,会创建一个空数组,赋值给elementData:this.elementData = new Object[initialCapacity];、elementData = Arrays.copyOf(elementData, size, Object[].class);。如果一个应用中有很多这样ArrayList空实例的话,就会有很多的空数组,无疑EE是为了优化性能,所有ArrayList空实例都指向同一个空数组。问题解决。
题外话:《Effective Java》第二版第43条:返回零长度的数组或集合,而不是null。难道因为这个建议让ArrayList空实例增加了,所以类库的编写者作出了这个优化,哈哈。
总结之EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别:EMPTY_ELEMENTDATA是为了优化创建ArrayList空实例时产生不必要的空数组,使得所有ArrayList空实例都指向同一个空数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了确保无参构成函数创建的实例在添加第一个元素时,最小的容量是默认大小10。
2018年7月22日16:48:05
ArrayList源码中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别的更多相关文章
- ArrayList源码中的两个值得注意的问题
1.“拖泥带水”的删除 测试代码: package com.demo; import java.util.ArrayList; public class TestArrayList { public ...
- Java入门系列之集合ArrayList源码分析(七)
前言 上一节我们通过排队类实现了类似ArrayList基本功能,当然还有很多欠缺考虑,只是为了我们学习集合而准备来着,本节我们来看看ArrayList源码中对于常用操作方法是如何进行的,请往下看. A ...
- Java中的容器(集合)之ArrayList源码解析
1.ArrayList源码解析 源码解析: 如下源码来自JDK8(如需查看ArrayList扩容源码解析请跳转至<Java中的容器(集合)>第十条):. package java.util ...
- JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)
文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...
- 顺序线性表 ---- ArrayList 源码解析及实现原理分析
原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...
- ArrayList 源码分析
ArrayList 源码分析 1. 结构 首先我们需要对 ArrayList 有一个大致的了解就从结构来看看吧. 1. 继承 该类继承自 AbstractList 这个比较好说 2. 实现 这 ...
- ArrayList源码分析超详细
ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要分析的类(ztrl+N查找ArraLi ...
- Java -- 基于JDK1.8的ArrayList源码分析
1,前言 很久没有写博客了,很想念大家,18年都快过完了,才开始写第一篇,争取后面每周写点,权当是记录,因为最近在看JDK的Collection,而且ArrayList源码这一块也经常被面试官问道,所 ...
- 面试必备:ArrayList源码解析(JDK8)
面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来 ...
随机推荐
- Python排序算法之直接插入排序
插入排序的主要思想是每次取一个列表元素与列表中已经排序好的列表段进行比较,然后插入从而得到新的排序好的列表段,最终获得排序好的列表. 比如,待排序列表为[49,38,65,97,76,13,27,49 ...
- Javascript Ajax异步读取RSS文档
RSS 是一种基于 XML的文件标准,通过符合 RSS 规范的 XML文件可以简单实现网站之间的内容共享.Ajax 是Asynchronous JavaScript and XML的缩写.通过 Aja ...
- shell脚本安装部署反向代理 监控进程 计划任务
1.编写脚本自动部署反向代理.web.nfs: 要求: I.部署nginx反向代理三个web服务,调度算法使用加权轮询: 反向代理服务器脚本配置脚本 #!/bin/bash #安装eple和nginx ...
- SpringBoot与异步任务、定时任务、邮件任务
异步任务 在需要开启异步的服务加上注解:@Async @Service public class AsyncService { //告诉SpringBoot这是一个异步任务,SpringBoot会自动 ...
- TensorFlow在win10上的安装与使用(二)
在上篇博客中已经详细的介绍了tf的安装,下面就让我们正式进入tensorflow的使用,介绍以下tf的特征. 首先tf有它独特的特征,我们在使用之前必须知晓: 使用图 (graph) 来表示计算任务, ...
- Linux/Unix系统编程手册 第二章:基本概念
本章预热与后续系统编程有关的概念. 术语“操作系统”通常包含2种含义:一是指完整的软件包,包括管理计算机资源的核心组件,已经附带的标准软件:二是独指管理硬件的内核. 内核具有诸多概功能,包括: 进程管 ...
- 再战CS231-数组的访问
1.切片访问和整形访问的区别 你可以同时使用整型和切片语法来访问数组.但是,这样做会产生一个比原数组低阶的新数组 import numpy as np # Create the following r ...
- 关于limit_req和limit_conn的区别
1,首先,limit_req和limit_conn两个模块都是为了来限流的,但是两者不在一个层面,为了搞清楚这个,必须先要弄清楚request和connection的区别,因为在很多情况下,我们把他们 ...
- Shell-help格式详解
前言 linux shell命令通常可以通过-h或--help来打印帮助说明,或者通过man命令来查看帮助,有时候我们也会给自己的程序写简单的帮助说明,其实帮助说明格式是有规律可循的 帮助示例 下面是 ...
- Redis—数据结构之sds
Redis是一个Key Value数据库.Redis有5种数据类型:字符串.列表.哈希.集合.有序集合.而字符串的底层实现方法之一就是使用sds.以下描述中请读者注意区分sds是指简单动态字符串这一数 ...