ArrayList 扩容机制的源码剖析

        在 Java 编程中,ArrayList是一个常用的集合类,它实现了List接口,底层基于数组实现。与普通定长数组不同,ArrayList能够根据元素的添加情况动态调整数组的大小,这就是其扩容机制。下面我们将深入剖析ArrayList扩容机制的源码。

        有的同学可能在别的地方听过一些ArrayList扩容,大部分会说ArrayList底层是数组结构的,默认长度为10,当数组加满后会自动扩容1.5倍。这样的说法不完全对。

        正确的步骤应该是如下的:        

一、ArrayList的构造方法

1. 无参构造

          我们可以自己实际的去探索一下,打开idea,按快捷键ctrl+n,输入ArrayList,选择All Places,点击Java.util那个。

        进入之后可以按ctrl+f12,按add,查找add方法,可以发现有很多add方法。

或者按Alt+7将整个大纲罗列出来,这里我们主要看的是空参构造

在这选中elementDate,按住ctrl+B,发现它是一个数组。

我们再看看DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个是什么,就会发现它是一个长度为0的数组,所以空参构造默认初始值为0。

当使用new ArrayList<>()创建对象时,底层会创建一个空数组。相关源码如下:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

这里的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object数组,ArrayList将其赋值给用于存储元素的elementData数组。

这里我将所有代码类全部汇总到一起,可以更好的理解过程。

2. 带初始容量的构造

当使用new ArrayList<>(int size)时,如果传入的size大于 0,会创建一个指定长度的数组;如果size等于 0,会创建一个空数组,与无参构造的情况相同;如果size小于 0,则会抛出IllegalArgumentException异常。源码如下:

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);}
}

其中EMPTY_ELEMENTDATA也是一个空数组,与DEFAULTCAPACITY_EMPTY_ELEMENTDATA有所区别,主要用于标识通过带容量参数且容量为 0 的构造方法创建的情况。

3. 带集合参数的构造

当使用new ArrayList<>(Collection<? extends E> c)时,会先将传入的集合转换为数组,然后判断数组长度:如果长度为 0,按无参构造方式创建空数组;如果不为 0,再判断传入的集合是否是ArrayList类型。如果是,直接将转换后的数组赋值给elementData;如果不是,使用Arrays.copyOf方法进行二次复制,以确保安全性和隔离性。相关源码如下:

public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {if (c.getClass() == ArrayList.class) {this.elementData = elementData;} else {this.elementData = Arrays.copyOf(elementData, size, Object[].class);}} else {this.elementData = EMPTY_ELEMENTDATA;}
}

二、添加元素与扩容触发

当调用add(E e)方法向ArrayList中添加元素时,可能会触发扩容机制。add方法的源码如下:

public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保有足够的容量来存储新元素elementData[size++] = e;return true;
}

其中ensureCapacityInternal(int minCapacity)方法用于确保内部数组有足够的容量来存储新元素。minCapacity表示所需的最小容量,这里是当前元素个数size加 1。

进入ensureCapacityInternal方法:

private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);
}

在这个方法中,如果elementData是默认的空数组(即通过无参构造创建的情况),会将minCapacity设置为默认容量DEFAULT_CAPACITY(值为 10)和minCapacity中的较大值。然后调用ensureExplicitCapacity方法来判断是否需要扩容。

ensureExplicitCapacity方法的源码如下:

private void ensureExplicitCapacity(int minCapacity) {modCount++;if (minCapacity - elementData.length > 0)grow(minCapacity);
}

这里通过比较所需的最小容量minCapacity和当前数组elementData的长度,如果minCapacity大于elementData.length,则说明当前数组容量不足,需要调用grow方法进行扩容。

三、扩容的核心方法grow

grow方法是ArrayList扩容的关键,其源码如下:

private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);  // 扩容为原来的1.5倍if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}

grow方法中,首先获取当前数组的容量oldCapacity,然后通过oldCapacity + (oldCapacity >> 1)计算出一个新的容量newCapacity,这里oldCapacity >> 1表示将oldCapacity右移一位,相当于除以 2,所以新容量是原来的 1.5 倍。

接着会进行两次判断:

  1. 如果newCapacity小于minCapacity,说明 1.5 倍扩容后的容量仍不满足需求,此时将newCapacity设置为minCapacity,即按需扩容。
  2. 如果newCapacity超过了ArrayList所能支持的最大数组大小MAX_ARRAY_SIZE(在Integer.MAX_VALUE - 8Integer.MAX_VALUE之间的一个值,具体取决于 JVM 的实现),则调用hugeCapacity方法来确定最终的容量。

最后,通过Arrays.copyOf方法将原数组的元素复制到新的、更大容量的数组中,完成扩容操作。

ArrayList的扩容机制通过巧妙的设计和源码实现,在保证能够动态存储元素的同时,尽量减少不必要的数组复制操作,提高了性能。深入理解其扩容机制,有助于我们在使用ArrayList时更好地进行性能优化和资源管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/69095.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 面试之结束问答

技术优化 线程池优化 设置最大线程数设置最小核心线程数设置额外线程存活时间选择线程池队列选择合适的线程池选择合适的饱和策略 锁优化 尽量不要锁住方法缩小同步代码块&#xff0c;只锁数据锁中尽量不要再包含锁将锁私有化&#xff0c;在内部管理锁进行适当的锁分解 HT…

【llm对话系统】大模型源码分析之 LLaMA 位置编码 RoPE

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Transformer 模型已经成为主流。然而&#xff0c;Transformer 本身并不具备处理序列顺序的能力。为了让模型理解文本中词语的相对位置&#xff0c;我们需要引入位置编码&#xff08;Positional Encoding&#xff09;…

【科研】 -- 医学图像处理方向,常用期刊链接

文章目录 文章目录 医学图像处理方向&#xff0c;期刊链接医学图像处理方向&#xff0c;会议 医学图像处理方向&#xff0c;期刊链接 Sicence https://www.science.org/ Nature https://www.nature.com/ Nature Communications https://www.nature.com/ncomms/ Nature Met…

The specified Gradle distribution ‘gradle-bin.zip‘ does not exist.

The specified Gradle distribution ‘https://services.gradle.org/distributions/gradle-bin.zip’ does not exist. distributionUrl不存在&#xff0c;关联不上&#xff0c;下载不了&#xff0c;那就匹配一个能下载的 distributionUrlhttps://services.gradle.org/distrib…

从零开始实现一个双向循环链表:C语言实战

文章目录 1链表的再次介绍2为什么选择双向循环链表&#xff1f;3代码实现&#xff1a;从初始化到销毁1. 定义链表节点2. 初始化链表3. 插入和删除节点4. 链表的其他操作5. 打印链表和判断链表是否为空6. 销毁链表 4测试代码5链表种类介绍6链表与顺序表的区别7存储金字塔L0: 寄存…

Cesium点集中获取点的id,使用viewer.value.entities.getById报错的解决方法

错误代码&#xff1a; viewer.value.entities.getById(pickedObject.id) 报错&#xff1a; 可以正常获取movement.position但是一直出现如下报错&#xff0c;无法获得航点的id&#xff0c;通过断点定位为 viewer.value.entities.getById(pickedObject.id)导致的报错 解决方…

java 进阶教程_Java进阶教程 第2版

第2版前言 第1版前言 语言基础篇 第1章 Java语言概述 1.1 Java语言简介 1.1.1 Java语言的发展历程 1.1.2 Java的版本历史 1.1.3 Java语言与C&#xff0f;C 1.1.4 Java的特点 1.2 JDK和Java开发环境及工作原理 1.2.1 JDK 1.2.2 Java开发环境 1.2.3 Java工作原理 1.…

ARM Linux Qt使用JSON-RPC实现前后台分离

文章目录 1、前言2、解决方案2.1、JSON-RPC2.2、Qt中应用JSON-RPC的框架图2.3、优点2.4、JSON-RPC 1.0 协议规范 3、程序示例3.1、Linux C&#xff08;只例举RPC Server相关程序&#xff09;3.2、Qt程序&#xff08;只例举RPC Client相关程序&#xff09; 4、编译程序4.1、交叉…

PyQt6/PySide6 的 QMainWindow 类

QMainWindow 是 PyQt6 或 PySide6 库中一个非常重要的类&#xff0c;它提供了一个主窗口应用程序的框架&#xff0c;该框架可以包含菜单栏、工具栏、状态栏以及中心部件等。QMainWindow 为 GUI 应用程序提供了基本的结构和布局管理功能&#xff0c;非常适合用来创建复杂的用户界…

9. k8s二进制集群之kube-controller-manager部署

同样在部署主机上创建证书请求文件(为之后的证书生成做准备)根据上面的证书文件创建证书(结果会在当前目录下产生kube-controller-manager证书)创建kube-controller-manager服务配置文件创建kube-controller-manager服务启动文件同步kube-controller-manager证书到对应mast…

教程 | i.MX RT1180 ECAT_digital_io DEMO 搭建(一)

本文介绍 i.MX RT1180 EtherCAT digital io DEMO 搭建&#xff0c;Master 使用 TwinCAT &#xff0c;由于步骤较多&#xff0c;分为上下两篇&#xff0c;本文为第一篇&#xff0c;主要介绍使用 TwinCAT 控制前的一些准备。 原厂 SDK 提供了 evkmimxrt1180_ecat_examples_digit…

ubuntu22.40安装及配置静态ip解决重启后配置失效

遇到这种错误&#xff0c;断网安装即可&#xff01; 在Ubuntu中配置静态IP地址的步骤如下。根据你使用的Ubuntu版本&#xff08;如 Netplan 或传统的 ifupdown&#xff09;&#xff0c;配置方法有所不同。以下是基于 Netplan 的配置方法&#xff08;适用于Ubuntu 17.10及更高版…

服务端渲染技术

一.JSP 1.jsp介绍,全称是java Server Pages ,java服务器页面,就是服务端渲染技术,html只能为用户提供静态数据,而JSP技术允许在页面中嵌套 java代码,jsp技术基于Servlet,Servlet很难对数据进行排版,而jsp就可以,可以理解为jsp就是对Servlet的包装. 2.jsp程序本质是java程序,无…

[23] cuda应用之 nppi 实现图像缩放

[23] cuda应用之 nppi 实现图像缩放 NPP&#xff08;NVIDIA Performance Primitives&#xff09;是一个由 NVIDIA 提供的库&#xff0c;专门用于加速图像和信号处理任务。NPP 提供了许多高效的图像处理函数&#xff0c;包括图像缩放。使用 NPP 实现图像缩放可以充分利用 GPU 的…

【产品经理学习案例——AI翻译棒出海业务】

前言&#xff1a; 本文主要讲述了硬件产品在出海过程中&#xff0c;翻译质量、翻译速度和本地化落地策略是硬件产品规划需要考虑的核心因素。针对不同国家&#xff0c;需要优化翻译质量和算法&#xff0c;关注市场需求和文化差异&#xff0c;以便更好地满足当地用户的需求。同…

CH340G上传程序到ESP8266-01(S)模块

文章目录 概要ESP8266模块外形尺寸模块原理图模块引脚功能 CH340G模块外形及其引脚模块引脚功能USB TO TTL引脚 程序上传接线Arduino IDE 安装ESP8266开发板Arduino IDE 开发板上传失败上传成功 正常工作 概要 使用USB TO TTL&#xff08;CH340G&#xff09;将Arduino将程序上传…

1.4 Go 数组

一、数组 1、简介 数组是切片的基础 数组是一个固定长度、由相同类型元素组成的集合。在 Go 语言中&#xff0c;数组的长度是类型的一部分&#xff0c;因此 [5]int 和 [10]int 是两种不同的类型。数组的大小在声明时确定&#xff0c;且不可更改。 简单来说&#xff0c;数组…

AI推理性能之王-Groq公司开发的LPU芯片

Groq公司开发的LPU&#xff08;Language Processing Unit&#xff0c;语言处理单元&#xff09;芯片是一种专为加速大规模语言模型&#xff08;LLM&#xff09;和其他自然语言处理任务而设计的新型AI处理器。以下是对其技术特点、性能优势及市场影响的深度介绍&#xff1a; 技…

C#中的委托(Delegate)

什么是委托? 首先,我们要知道C#是一种强类型的编程语言,强类型的编程语言的特性,是所有的东西都是特定的类型 委托是一种存储函数的引用类型,就像我们定义的一个 string str 一样,这个 str 变量就是 string 类型. 因为C#中没有函数类型,但是可以定义一个委托类型,把这个函数…

rk3506 sd卡启动

1 修改系统配置文件,打开ext4 #SDMMC RK_ROOTFS_TYPE"ext4" RK_ROOTFS_INSTALL_MODULESy RK_WIFIBT_CHIP"AIC8800" # RK_ROOTFS_LOG_GUARDIAN is not set RK_UBOOT_CFG_FRAGMENTS"rk3506_tb" RK_UBOOT_SPLy RK_KERNEL_CFG"rk3506_defconfi…