Java中的ArrayList容量及扩容方式
作者:zx2015216856 发布时间:2023-10-17 17:24:40
查看JDK1.8 ArrayList的源代码
1、默认初始容量为10
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
2、最大容量为 Integer.MAX_VALUE - 8
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
原因:之前参考别人的,有待求证:
数组对象有一个额外的元数据,用于表示数组的大小;
数组长度size为int类型,共32位,有一位符号位,所以最大长度为Integer.MAX_VALUE=2^31= 2,147,483,648;
8bytes用来存储size;
3、扩容方式:
(1)首先传递进来一个希望的最小容量minCapacity;
(2)新容量newCapacity = oldCapacity + (oldCapacity >> 1),即新容量等于原容量的1.5倍;
(3)如果minCapacity > newCapacity ,newCapacity = minCapacity ;
(4)如果 newCapacity > 最大容量 MAX_ARRAY_SIZE ,newCapacity = hugeCapacity(minCapacity);
(5)以新容量拷贝原数据
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
Java ArrayList() 扩容原理
平常都是直接使用 ArrayList(),今天特地看一下 ArrayList() 的扩容原理。
先看下 ArrayList 的属性以及构造方法,这个比较重要
先看下属性
// ArrayList 默认容量大小
private static final int DEFAULT_CAPACITY = 10;
// 一个共享的空数组, 在空实例时使用
private static final Object[] EMPTY_ELEMENTDATA = {};
// 一个共享的空数组, 在使用默认 size 的空实例时使用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
存储 ArrayList 元素的数组缓冲区
重点1: ArrayList 的容量是数组缓冲区的长度
重点2: 从这个元素也可以看的出来 ArrayList() 的底层就是一个 Object[]
add 第一个元素时, 任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将被扩展为 DEFAULT_CAPACITY
*/
transient Object[] elementData;
// ArrayList 的大小, 我们平常使用的 list.size() 底层就是记录的这个 size
private int size;
// ArrayList 最大
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
再看构造器,带参构造器
/*
带参构造器, 参数为容量大小, 例如: 初始化一个容器为 20 类型为 Integer 的 ArrayList
ArrayList<Integer> list = new ArrayList<>(20);
*/
public ArrayList(int initialCapacity) {
/*
初始化容量 > 0, elementData 初始化为初始化容量大小的数组
初始化容量 = 0, elementData = EMPTY_ELEMENTDATA (空数组)
初始化容量 < 0, 直接抛出异常
*/
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
参数为 Collection 的构造器
/*
将一个参数为 Collection 的集合转换为 ArrayList
*/
public ArrayList(Collection<? extends E> c) {
// Collection 转换为数组 Object[] 类型
elementData = c.toArray();
// 判断当前对象大小是否和 Collection 长度相等并且不等于 0, false 的话 elementData 等于空数组了
if ((size = elementData.length) != 0) {
// c.toArray() 可能不会正确地返回一个 Object[] 数组,所以使用 Arrays.copyOf()
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
不带参构造器
/*
不带参构造器就像我们平时使用一样, 直接 new 一个 ArrayList 不需要传递任何参数
构造方法中直接将 elementData 初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA (空数组)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
看到这里就发现,带参的构造器我们可以直接传递参数,而默认的构造器怎么怎么样初始化容量大小的呢?
add() 方法可以直接得到答案。
public boolean add(E e) {
// 这一行是关键, 看下面
ensureCapacityInternal(size + 1);
// 将元素追加到集合的末尾 假如当前 size = 10 size++ 追加到第 11 位
elementData[size++] = e;
return true;
}
ensureCapacityInternal() 方法调用
private void ensureCapacityInternal(int minCapacity) {
/*
calculateCapacity() 方法, 刚初始化时会返回 10, 其他情况返回当前 size + 1
ensureExplicitCapacity() 方法, 调用扩容
*/
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/*
使用无参构造器创建创建 ArrayList 的集合, 此时一定是相等的
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
/*
两数相比返回最大值, 此时 Math.max(10, 1);
默认容量, 由此而来
*/
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 不相等的话只有返回当前的 size + 1
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 增量, 记录修改/更新次数
modCount++;
// 初始化: 10 - 0 > 0
// 其他: size + 1 > 0
if (minCapacity - elementData.length > 0)
// 扩容操作
grow(minCapacity);
}
private void grow(int minCapacity) {
// 老的长度, 初始化时为 0,
int oldCapacity = elementData.length;
// 新的长度此时 0 + (0 >> 1), newCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 初始化场景: 0 - 10 < 0 ? true newCapacity = 10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 初始化场景: 10 - 2147483639 > 0 返回 false
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 超大长度才可以执行这个方法, 必须大于 MAX_ARRAY_SIZE 一般不会
newCapacity = hugeCapacity(minCapacity);
// 复制原数组中的元素, 并扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
上看说的是初始化场景,下面看一下其他场景,也是相当简单
private void ensureCapacityInternal(int minCapacity) {
/*
calculateCapacity() 方法, 正常扩容返回 size + 1, 比如 10 + 1, 因为默认长度为 10 当再次新增数据时就会出发扩容
ensureExplicitCapacity() 方法, 调用扩容
*/
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
/*
elementData 不等于空数组
返回当前的 size + 1, 即 10 + 1 返回 11
*/
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 增量, 记录修改/更新次数
modCount++;
// 其他: 11 - 10 > 0 true, 触发扩容, 如果当前下表是 5 的话 5 + 1 =6, 6 < 10 是, 此时不会出发扩容
if (minCapacity - elementData.length > 0)
// 扩容操作
grow(minCapacity);
}
private void grow(int minCapacity) {
// 老的长度 10
int oldCapacity = elementData.length;
// 新的长度此时 10 + (10 >> 1), newCapacity = 15
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 15 - 11 < 0 ? false
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 15 - 2147483639 > 0 返回 false
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 超大长度才可以执行这个方法, 必须大于 MAX_ARRAY_SIZE 一般不会
newCapacity = hugeCapacity(minCapacity);
// 复制原数组中的元素, 并扩容 newCapacity = 15
elementData = Arrays.copyOf(elementData, newCapacity);
}
结论
1、 触发扩容的关键是
当前 size + 1 是否大于当前容量,如果大于容量则证明,集合不够用了,需要扩容。如果小与当前容量则证明集合还有容量不需要扩容!
2、 每次扩容的大小是
oldCapacity + (oldCapacity >> 1) 即: 10 + (10 >> 1)
即:当前容量的 1.5 倍!
来源:https://blog.csdn.net/zx2015216856/article/details/82597104


猜你喜欢
- 1. 我们可以通过将字符强转为int型进行输出那么在控制台中我们将会得到字符的ascii值,这里我们使用nextLine()方法来接收字符串
- 本文实例讲述了Java中对象的比较操作。分享给大家供大家参考,具体如下:一 点睛在Java中,有两种方式可用于对象间的比较:利用"
- 本文通过介绍了Asp.Net中MVC缓存的种类,以及他们之间的区别等内容,让学习者能够深入的了解MVC缓存的原理机制,以下是具体内容:缓存是
- IOC创建对象的方式一、 使用无参构造创建对象(默认方式)创建实体类注意:属性必须要有set方法,来完成注入public class Use
- 知识点:1.使用SQL Helper创建数据库2.数据的增删查改(PRDU:Put、Read、Delete、Update)背景知识:上篇文章
- 一、使用方法查找DLL文件,通过Reflection反射类库里的各种方法来操作dll文件二、步骤加载DLL文件Assembly assemb
- Android:Field can be converted to a local varible.的解决办法前言:使用 Android S
- 一.背景在复习《C++基础与提高》时,自己实现运算符重载(i++)时,几次都报错。其实还是自己对运算符重载这一部分内容理解得不够透彻,于是再
- 刚刚看MSDN的一个例子无意发现的小技巧,大家一看就明白了,不过好像蛮有用的,先记下咯,以后慢慢研究。using System;namesp
- 本文介绍了Android 仿微信自定义数字键盘的实现代码,分享给大家,希望对大家有帮助最终效果:实现这个自定义键盘的思路很简单:要写出一个数
- 概述这里是Mac环境,如果是window环境的同学,在环境搭建和工具上可以选择Window环境的。先看看需要到的工具;1、apktool:h
- 目前很多网页都有滑动验证,目的就是防止不良爬虫扒他们网站的数据,我这次本着学习的目的使用Java和selenium学习解决滑动验证的问题,前
- 前言服务消费者调用服务提供者的时候使用RestTemplate技术存在不便之处:拼接urlrestTmplate.getForObJect这
- 1、说明使用Directory类对指定文件夹下的今天或者更早日期之前的文件进行删除。2、代码//文件夹路径string strFolderP
- 一、前言装饰模式实际上是一直提倡的组合代替继承的实践方式,个人认为要理解装饰者模式首先需要理解为什么需要组合代替继承,继承又是为什么让人深恶
- 本文以C#和vb.net代码示例展示如何来获取Excel工作表中图片的坐标位置。这里的坐标位置是指图片左上角顶点所在的单元格行和列位置,横坐
- 在之前的文章中,我们介绍了JDK14中jstat工具的使用,本文我们再深入探讨一下jstack工具的使用。jstack工具主要用来打印jav
- 本文实例为大家分享了ActionBar实现tab导航效果的具体代码,供大家参考,具体内容如下先来说一说基础知识:一、基本使用方法1.获取Ac
- 1、概括在博客中,我们将讨论如何让Spring Security OAuth2实现使用JSON Web Tokens。2、Maven 配置首
- 目录基本查询延迟查询属性类型筛选复合from子句多级排序分组联合查询-join合并-zip()分区(分页)并行linq取消长时间运行的并行l