探秘ArrayList源码:Java动态数组的背后实现

news2024/11/23 21:17:41

探秘ArrayList源码:Java动态数组的背后实现

  • 一、成员变量
  • 二、构造器
    • 1、默认构造器
    • 2、带初始容量参数构造器
    • 3、指定collection元素参数构造器
  • 三、add()方法扩容机制
  • 四、场景分析
    • 1、对于ensureExplicitCapacity()方法
      • 1.1 add 进第 1 个元素到 ArrayList 时
      • 1.2 当 add 第 2 个元素时
      • 1.3 直到添加第 11 个元素
    • 2、对于grow() 方法:
      • 2.1 当 add 第 1 个元素时
      • 2.2 当 add 第 11 个元素进入 grow 方法时
  • 五、心得体会
  • 六、源码简易流程图



一、成员变量

读者需先对源码的成员变量阅览一遍,看个眼熟,有助于后面源码的理解

private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组(用于空实例)。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

     //用于默认大小空实例的共享空数组实例。
      //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储ArrayList元素的数组缓冲区
     */
    transient Object[] elementData; 

    /**
     * ArrayList 所包含的元素个数
     */
    private int size;

二、构造器

1、默认构造器

/**
 *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

以无参数构造方法创建 ArrayList时,实际上初始化赋值的是一个空数组。此时并没有为它创建对象,当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。

2、带初始容量参数构造器

/**
 * 带初始容量参数的构造函数。(用户自己指定容量)
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {//初始容量大于0
        //创建initialCapacity大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等于0
        //创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

3、指定collection元素参数构造器

/**
*构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
*如果指定的集合为null,throws NullPointerException。
*/
 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;
    }
}

三、add()方法扩容机制

在进入ArrayList的核心源码扩容机制前,我们首先需要对源码中涉及到的一些变量进行一个初步的了解,这将有助于你对源码的深入了解

  1. minCapacity:数组所需的最小容量
  2. elementData:存储ArrayList元素的数组缓冲区

在这里插入图片描述

public boolean add(E e) {
   ensureCapacityInternal(size + 1);  // size = 0
   elementData[size++] = e;
   return true;
}
private void ensureCapacityInternal(int minCapacity) {//minCapacity = 1
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//elementData = {}
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //判断elementData是否为空数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//DEFAULTCAPACITY_EMPTY_ELEMENTDATA =  {}
        return Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY = 10;minCapacity = 1
    }
    return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {//minCapacity = 1
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//ensureExplicitCapacity(10)
}
private void ensureExplicitCapacity(int minCapacity) {//minCapacity = 10
    modCount++;

    if (minCapacity - elementData.length > 0)//elementData.length = 0
        grow(minCapacity);
}
private void grow(int minCapacity) {//minCapacity = 10
    int oldCapacity = elementData.length;//oldCapacity = 0
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity  = 1.5*oldCapacity = 0
    if (newCapacity - minCapacity < 0)//minCapacity = 10
        newCapacity = minCapacity;//newCapacity = 10
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //利用Arrays.copyOf()方法进行扩容
    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;
 }
  1. 增加一个新元素,此时所需的最小容量是 minCapacity = size+1
  2. 首先判断底层数组 elementData 是否是空数组
    • 如果是空数组,则更新 minCapacity 为默认容量10,
    • 如果不是空数组,则对 minCapacity 不做变更
  3. 判断所需最小容量 minCapacity 是否大于缓冲数组 elementData 的长度:
    • 如果大于,则进行扩容 grow()
    • 否则不做处理
  4. 扩容 grow()中,首先一上来就将 elementData 的长度扩长为原来长度的1.5倍,再对扩容后的 elementData 的长度和所需最小的容量 minCapacity进行判断
    • 如果扩容后的 elementData 的长度还小于 minCapacity 的长度,说明还是不够,此时就直接将minCapacity的长度赋值给elementData
    • 否则的话直接进行下一步即可
  5. 最后需要对 elementData 的长度进行一个是否超过最大限度值MAX_ARRAY_SIZE判断
    • 如果超过最大限度值,就看看所需的最小容量minCapacity是否大于最大限度值Integer.MAX_VALUE
    • 如果不是,就将数组的长度扩容为数组的最大限度值MAX_ARRAY_SIZE,如果是,则返回Integer.MAX_VALUE

四、场景分析

1、对于ensureExplicitCapacity()方法

1.1 add 进第 1 个元素到 ArrayList 时

当我们要 add 进第 1 个元素到 ArrayList 时,elementData.length 为 0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为 10。此时,minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法。

1.2 当 add 第 2 个元素时

当 add 第 2 个元素时,minCapacity 为 2,此时 elementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。

1.3 直到添加第 11 个元素

minCapacity(为 11)elementData.length(为 10)要大。进入 grow 方法进行扩容。

2、对于grow() 方法:

2.1 当 add 第 1 个元素时

oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 hugeCapacity 方法。数组容量为 10,add 方法中 return true,size 增为 1。

2.2 当 add 第 11 个元素进入 grow 方法时

newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。以此类推······

五、心得体会

  1. 变量 minCapacity 理解为我们添加元素进ArrayList集合中,底层数组所需的最小容量
  2. 变量 elementData.length 理解为我们添加元素进ArrayList集合中,底层数组的最大限度容量
  3. 当最小容量 minCapacity> 最大限度容量 elementData.length ,必定会触发扩容机制。
  4. 我们总是频繁地向ArrayList集合添加元素,开发人员也想到了这点,所以在ArrayList集合的扩容机制中,当我们添加第一个元素时,直接就把minCapacity设置为10,此处可以理解为,因为我们后续要频繁添加元素,为了不总是触发该集合的扩容机制,便“谎称”所需的最小容量是10,所以系统就直接把elementData.length设置为10

六、源码简易流程图

在这里插入图片描述

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

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

相关文章

MQTT的理解和使用

MQTT是一种基于发布/订阅模式的轻量协议&#xff0c;该协议基于TCP/IP协议上&#xff0c;由IBM在1999年发布。 流程理解&#xff1a;订阅者在订阅时会选择主题&#xff08;Topic&#xff09;和服务质量&#xff08;QoS&#xff09;&#xff0c;然后发布者发布消息&#xff0c…

matlab超前-滞后校正

1控制系统的校正 系统性能 稳定性、准确性、快速性 动态性能-超前校正 阶跃曲线、频域&#xff08;bode图&#xff09;、根轨迹&#xff08;增加零点-根轨迹左移稳定性提高&#xff09;、PID控制&#xff08;PD&#xff09; 静态性能-滞后校正 阶跃曲线、频域&#xff08…

Flink CDC MongoDB 联合实时数仓的探索实践

摘要&#xff1a;本文整理自 XTransfer 技术专家, Flink CDC Maintainer 孙家宝&#xff0c;在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分&#xff1a; MongoDB 在实时数仓的探索 MongoDB CDC Connector 的实现原理和使用实践 FLIP-262 MongoDB…

Spring MVC拦截器和跨域请求

一、拦截器简介 SpringMVC的拦截器&#xff08;Interceptor&#xff09;也是AOP思想的一种实现方式。它与Servlet的过滤器&#xff08;Filter&#xff09;功能类似&#xff0c;主要用于拦截用户的请求并做相应的处理&#xff0c;通常应用在权限验证、记录请求信息的日志、判断用…

多肽试剂1801415-23-5,Satoreotide,UNII-S58172SSTS,应用在多肽标记及修饰上

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ Satoreotide&#xff0c;UNII-S58172SSTS Product structure Product specifications 1.CAS No&#xff1a;1801415-23-5 2.Molecular formula&#xff1a;C58H72ClN15O14S2 3.Molecular weight&#xff1a;1302.9 4.Packa…

【C++详解】——C++11

目录 C简介 统一的列表初始化 {}的初始化 initializer_list容器 声明 auto decltype nullptr 范围for C简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了 C98称为C11之前的最新C标准名称。 不过由于C03(TC1)主…

STM32 串口 DMA 接收任意长度数据

DMA 局限性 DMA 传输完成会产生中断告知 CPU&#xff0c;这对于固定长度的数据是没什么问题的。但是对于不定长的数据就不行了&#xff0c;DMA 一定要接收到足够多&#xff08;设定的长度&#xff09;的数据时才产生完成中断&#xff0c;如果接收到的数据量小于设定的长度&…

Linux的基础配置

配置篇 前置步骤&#xff1a;先租一个服务器或装个Linux再或者虚拟机都可以 1.安装gcc,g,gdb 在Linux下我们能用什么工具来编译所编写好的代码呢&#xff0c;其实Linux下这样的工具有很多&#xff0c;但我们只介绍两款常用的工具&#xff0c;它们分别是gcc和g. gcc和g的主要…

解决端口占用

解决办法&#xff1a; 1、换一个其它未被占用的端口 2、端口被占用了&#xff0c;先看下是哪个程序再用&#xff0c;停掉就OK了 下面演示结束端口被占用的程序的过程&#xff1a; 1、查看被占用的端口的进程 netstat -aon|findstr 端口号2、根据PID找到占用此端口的进程 ta…

Redis单机伪集群配置与搭建

目录 1. 复制6个redis配置文件到当前目录下 2. 启动每个节点 3. 判断集群是否可用 4. 初始化集群 5. 查看集群信息 6. 获取与插槽对应的节点 &#xff08;1&#xff09;手动重定向 &#xff08;2&#xff09;自动重定向 7. 故障恢复 需求&#xff1a;配置一个三主三从的集…

为什么ConcurrentHashMap不允许插入null值而HashMap可以?

为什么ConcurrentHashMap不允许插入null值而HashMap可以&#xff1f; 文章目录 为什么ConcurrentHashMap不允许插入null值而HashMap可以&#xff1f;HashMap源码ConcurrentHashMap源码为什么ConcurrentHashMap需要加空值校验呢&#xff1f;二义性问题测试代码代码分析测试结果结…

纯css3实现水波纹从中心向四周扩散动画

纯css3实现水波纹从中心向四周扩散动画 效果可用于pc端或移动端,引导用户点击,间接带来一定的转化率 示例效果 示例代码 <template><div class"zanbtn-wrap"><div click"handleClick(https://pay.aikelaidev.cn/paypage/?merchant35bdYxSx7dCUr…

子网划分路由网卡

1."IPv4 CIDR" "IPv4 CIDR" 是与互联网协议地址&#xff08;IP address&#xff09;和网络的子网划分有关的概念。 - "IPv4" 代表 "Internet Protocol version 4"&#xff0c;也就是第四版互联网协议&#xff0c;这是互联网上最广泛使…

IPv4 与 IPv6:网络协议的差异和转换方法

在网络的世界里&#xff0c;IPv4 和 IPv6 这两个协议就像是两个不同的王国&#xff0c;各自拥有着独特的领土和规则。虽然它们都是为了互联网的发展而存在&#xff0c;但它们之间究竟有哪些差异&#xff0c;以及如何在这两个王国之间穿梭&#xff0c;仍然是很多网络小白们的困惑…

Spring使用注解存储Bean对象

文章目录 一. 配置扫描路径二. 使用注解储存Bean对象1. 使用五大类注解储存Bean2. 为什么要有五大类注解&#xff1f;3.4有关获取Bean参数的命名规则 三. 使用方法注解储存Bean对象1. 方法注解储存对象的用法2. Bean的重命名 在前一篇博客中&#xff08; Spring项目创建与Bean…

Excel中Vlookup

VLOOKUP($A:$A,Sheet3!$A:$D,COLUMN(Sheet3!B1),FALSE) ps: 1.按F4&#xff0c;锁定第一个和第二个参数 2.第二个参数&#xff0c;要选择全部范围(包括被查找列&#xff0c;以及查找内容) 2.第三个参数用column&#xff08;&#xff09;函数&#xff0c;第三列不要锁定 3.…

【Linux】自动化构建工具-make/Makefile详解

前言 大家好吖&#xff0c;欢迎来到 YY 滴 Linux系列 &#xff0c;热烈欢迎&#xff01;本章主要内容面向接触过Linux的老铁&#xff0c;主要内容含 欢迎订阅 YY 滴Linux专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 订阅专栏阅读&#xff1a;YY的《…

Alpha-GO打败⼈类的秘籍- 强化学习(Reinforcement Learning)

为了深⼊理解强化学习&#xff08;Reinforcement Learning&#xff0c;简称RL&#xff09;这⼀核⼼概念&#xff0c;我们从⼀个⽇常游戏的例⼦出发。在“贪吃蛇”这个经典游戏中&#xff0c;玩家需要掌控⼀条蛇&#xff0c;引导它吞吃屏幕上出现的各种果实。每次成功捕获果实&a…

CSS层叠

声明冲突 同一个样式&#xff0c;多次应用到同一个元素。 此时可以看到我们的a元素的样式和浏览器的默认样式发生了冲突 层叠 解决声明冲突的过程&#xff0c;浏览器会自动处理&#xff08;权重计算&#xff09;&#xff0c;权重大的获胜 1.比较重要性 重要性从高到低 1&…

Docker 教程

Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&a…