简介

  来源:博客园    作者:吾王彦

  博客链接:https://www.cnblogs.com/qinjunlin/p/13724987.html

  ArrayList动态数组,是 java 中比较常用的数据结构。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。本随笔主要讲述ArrayList的扩容机制以及它的底层实现。如果懒得看整个分析过程,你可以直接查看文章最后的总结

成员变量

 1    private static final int DEFAULT_CAPACITY = 10;  //默认的初始容量为10
2
3 private static final Object[] EMPTY_ELEMENTDATA = {}; //空数组
4
5 /**
6 *与默认的初始容量DEFAULT_CAPACITY区分开来,
7 *简单来讲就是第一次添加元素时知道该 elementData
8 *从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
9 **/
10 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组,与EMPTY_ELEMENTDATA空数组区分
11
12 /**
13 * 存储ArrayList元素的数组缓冲区。
14 * ArrayList的容量是这个数组缓冲区的长度。
15 *任何空的ArrayList 即 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
16 *当添加第一个元素时,将扩展为DEFAULT_CAPACITY。
17 **/
18 transient Object[] elementData;
19
20 private int size; //实际元素个数

构造函数

有参构造

1 /**
2 * 构造一个初始容量为10的空ArrayList
3 */
4 public ArrayList() {
5 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
6 }

这里需要注意,注释(是我翻译后的)是说构造一个容量大小为 10 的空的 list 数组,但这里构造函数了只是给 elementData 赋值了一个空的数组,实际上并未开始扩容,这时候的容量还是0,真正开始扩容其实是在第一次添加元素时才容量扩大至 10 的。

有参构造函数

下面的代码是构造一个大小为 initialCapacity 的 ArrayList

 1 public ArrayList(int initialCapacity) {
2 if (initialCapacity > 0) { //当 initialCapacity 大于零时初始化一个大小为 initialCapacity 的 object 数组并赋值给 elementData
3 this.elementData = new Object[initialCapacity];
4 } else if (initialCapacity == 0) { //当 initialCapacity 为零时则是把 空数组EMPTY_ELEMENTDATA 赋值给 elementData
5 this.elementData = EMPTY_ELEMENTDATA;
6 } else {
7 throw new IllegalArgumentException("Illegal Capacity: "+
8 initialCapacity);
9 }
10 }

使用指定 Collection 来构造 ArrayList 的构造函数。将 Collection 转化为数组并赋值给 elementData。

 1 public ArrayList(Collection<? extends E> c) {
2 elementData = c.toArray();
3 if ((size = elementData.length) != 0) {
4 // c.toArray might (incorrectly) not return Object[] (see 6260652)
5 if (elementData.getClass() != Object[].class) //判断 elementData 的 class 类型是否为 Object[],不是的话则做一次转换
6 elementData = Arrays.copyOf(elementData, size, Object[].class);//
7 } else {
8 // size 为零,则将空数组赋给elementData,相当于new ArrayList(0)
9 this.elementData = EMPTY_ELEMENTDATA;
10 }
11 }

产生扩容的操作

add方法

 1 public boolean add(E e) {
2 ensureCapacityInternal(size + 1); //每次添加元素到集合中时都会先确认下集合容量大小
3 elementData[size++] = e; //size 自增 +1。
4 return true;
5 }
6
7 //确认容量大小
8 private void ensureCapacityInternal(int minCapacity) {
9 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
10 }
11
12 //返回容量大小
13 private static int calculateCapacity(Object[] elementData, int minCapacity) {
14 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
15 return Math.max(DEFAULT_CAPACITY, minCapacity); //这里其实才是真正的开始初始化数组的容量大小为10
16 }
17 return minCapacity;
18 }
19
20 //根据确认的容量的大小判断是否进行扩容
21 private void ensureExplicitCapacity(int minCapacity) {
22 modCount++; //记录操作次数
23
24 // overflow-conscious code
25 if (minCapacity - elementData.length > 0)
26 grow(minCapacity); //调用扩容方法
27 }

由上面的源代码可知数组的容量大小的初始化是在第一次添加元素的时候才开始的。calculateCapacity方法中有一个判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA   ,在第一次添加元素的时候,集合本身就是空的,所以会 就取 DEFAULT_CAPACITY minCapacity 的最大值也就是 10。

初始化完容量后,之后的每次添加元素都会走一遍上面代码的流程,就是每次添加元素前都会确认容量,然后根据minCapacity是否大于 elementData 的长度来决定是否对集合进行扩容。

grow扩容

 1 //根据确认的容量的大小判断是否进行扩容
2 private void ensureExplicitCapacity(int minCapacity) {
3 modCount++; //记录操作次数
4
5 // overflow-conscious code
6 if (minCapacity - elementData.length > 0)
7 grow(minCapacity); //调用扩容方法
8 }
9
10 //扩容函数
11 private void grow(int minCapacity) {
12 // overflow-conscious code
13 int oldCapacity = elementData.length;
14 int newCapacity = oldCapacity + (oldCapacity >> 1); //这里NewCapacity为原来容量的1.5倍
15 if (newCapacity - minCapacity < 0)
16 newCapacity = minCapacity;
17 if (newCapacity - MAX_ARRAY_SIZE > 0)
18 newCapacity = hugeCapacity(minCapacity);
19 // minCapacity is usually close to size, so this is a win:
20 elementData = Arrays.copyOf(elementData, newCapacity);
21 }
1 //扩容过大,或者所需容量过大则调用此方法
2 private static int hugeCapacity(int minCapacity) {
3 if (minCapacity < 0) // overflow
4 throw new OutOfMemoryError();
5 return (minCapacity > MAX_ARRAY_SIZE) ?
6 Integer.MAX_VALUE :
7 MAX_ARRAY_SIZE;
8 }

有代码可知,这里的扩容是扩容至原来容量的 1.5 倍。为什么是1.5倍呢?可以看看下面的这个

以无参构造为例。

int oldCapacity = elementData.length;  //原容量为10,二进制表示为1010

int newCapacity = oldCapacity + (oldCapacity >> 1);   //此处将原来的1010右移一位,变成101,即10进制的5

所以 newCapacity  = 10 + 5,即是15 。所以1.5倍是这样来的。

可以理解“>>”右移符号相当于除以2

其次,扩容1.5并不一定是最终的容量大小。因为还有两个判断。

1、如果1.5倍太小的话,则将我们所需的容量大小赋值给newCapacity

2、1.5倍太大或者我们需要的容量太大,则调用 hugeCapacity方法。

太大究竟是多大?请看下面

hugeCapacity方法。中的参数分析

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //ArrayList的成员变量
Integer.MAX_VALUE的大小为2的31次-1,即2147483647

所以你说大不大?嘻嘻

总结

  首先,定义一个ArrayList,常用的有两种方式。一种是使用无参构造,即new ArrayList();另一种是使用有参构造,new ArrayList(6),new一个初始容量大小为6的ArrayList。

  使用无参构造定义的ArrayList,在没有添加元素之前,初始容量为0,只有第一次添加元素的时候才会初始化初始容量为10,每次添加元素前都会确认集合的容量大小

如果容量不够用会进行扩容。一般会扩容至原来的1.5倍,如果所需容量太大,会扩容到2的31次-1,或者(2的31次-1)-8(详细看上面代码分析)。

  使用有参构造定义ArrayList,初始容量大小就是有参定义的大小,new ArrayList(6) ,初始容量就是6 。扩容机制有参无参构造都是一样的。

  看到这里,你是否还有个疑问,为什么是设计成扩容1.5倍呢?因为,因为他喜欢啊。哈哈哈哈哈。。。。。。。

JDK1.6 ArrayList无参构造,默认容量是10

JDK1.7 ArrayList无参构造,默认容量才改为是0,只有在add()时才初始化为10

这里讨论的是JDK1.8的版本的



最新文章

  1. nodejs初学————安装篇(iis8.5+windows8.1)
  2. 带Left Join的SQL语句的执行顺序
  3. dbcp 是什么
  4. android之多媒体篇(二)
  5. c#画正弦波
  6. 转:VC++获取屏幕大小第一篇 像素大小GetSystemMetrics
  7. 不要伤害指针(5)--void和void指针详解
  8. js 各种循环的区别与用法(for in,forEach,for of)
  9. centos6.7安装openblas错误
  10. cmd执行超大sql文件
  11. docker 安装elasticSearch7.0.0
  12. git从远程分支clone项目到本地,切换分支命令,其他常用命令
  13. Mybatis Update statement Date null
  14. Hashtable数据存储结构-遍历规则,Hash类型的复杂度为啥都是O(1)-源码分析
  15. Nginx CONTENT阶段 static模块
  16. Linux 之 Makefile 报错
  17. 空间谱专题13:联合解算DOA(ML/AP)
  18. Hibernate的状态
  19. jsp传给java属性,java生成json串,方便以后取出来
  20. Python开发一个多并发的FTP SERVER

热门文章

  1. 第七届蓝桥杯JavaB组——第6题方格填数
  2. SpringBoot(八):SpringBoot中配置字符编码 Springboot中文乱码处理
  3. Excel技巧—开始菜单之格式刷六大功能
  4. 摄像机+LookAt矩阵+视角移动+欧拉角
  5. 《进击吧!Blazor!》系列入门教程 第一章 6.安全
  6. 2020年12月-第01阶段-前端基础-表格 table
  7. 七种方案!探讨Redis分布式锁的正确使用姿势
  8. 掌握HTTP原理
  9. 使用css3和javascript开发web拾色器实例
  10. 卷积神经网络学习笔记——轻量化网络MobileNet系列(V1,V2,V3)