【JavaSE】ArrayList的扩容机制源码分析

news2024/11/21 1:41:13

文章目录

  • 1. ArrayList概述
  • 2. ArrayList构造方法源码分析
  • 3. ArrayList.add()源码分析
  • 4. ArrayList.addAll()源码分析
  • 5. 总结

1. ArrayList概述

ArrayList是Java集合框架中比较常用的一个数据结构了,它底层是基于数组实现的。数组是固定大小的,但是ArrayList的扩容机制可以实现容量大小的动态变化。

数组的容量是在定义的时候确定的,如果数组满了,再插入,就会数组溢出。所以在插入时候,会先检查是否需要扩容,如果当前容量+1超过数组长度,就会进行扩容。

ArrayList的扩容是创建一个1.5倍的新数组,然后把原数组的值拷贝过去。

image-20230201153601407

ArrayList添加元素有下面两种方式

  1. public boolean add(E e)
  2. public boolean addAll(Collection<? extends E> c)

下面进行源码分析


2. ArrayList构造方法源码分析

创建ArrayList集合的时候,会调用其构造方法

//存放元素的数据
transient Object[] elementData;
//默认空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//集合长度
private int size;

//无参构造
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//指定元素长度构造
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(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}
  1. 无参构造:无参构造会使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA作为存放元素的数据的数组,也就是默认集合底层的数组长度为0。
  2. 指定元素长度构造:指定元素长度构造则是使用构造参数initialCapacity作为数组的长度。this.elementData = new Object[initialCapacity];
  3. 构造参数为集合:使用构造参数集合c的长度作为底层数组elementData的长度,并将集合的元素拷贝成一个新数组赋值给elementData

3. ArrayList.add()源码分析

先准备一段添加数据进行的代码,添加的数据长度为15。

public class TestArrayList {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 15; i++) {
            list.add(i);
        }
    }
}

首先,创建ArrayList集合的时候,会调用其无参构造方法,ArrayList底层数组为长度为0的空数组。

下面是add方法的源码

public boolean add(E e) {
    //检查是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

在添加元素进集合集合之前,需要先调用ensureCapacityInternal方法,检查是否要扩容。

参数为底层存储数据的数组的长度+1

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

ensureCapacityInternal里面ensureExplicitCapacity方法。

在执行ensureExplicitCapacity之前需要调用calculateCapacity方法计算需要扩容的长度。

//默认的初始化容量大小
private static final int DEFAULT_CAPACITY = 10;
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

calculateCapacity方法有两个参数,elementData是底层用来存储数据的数组,而minCapacity就是新的数组长度。

在这个方法里,首先判断一下elementData是不是默认的空数组(也就是是否和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一个对象)**,如果是,则取新数组的长度和默认的初始化容量的长度的最大值返回。**也就是说,如果集合中第一次添加元素,那么默认的容量大小为10。

如果不是默认的空数组,则将minCapacity也就是新的数组长度返回。

//该参数是记录列表结构以被修改的次数
protected transient int modCount = 0;
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

接着比较现在新数组长度minCapacity和底层数组长度elementData.length的大小

如果minCapacity小于等于elementData.length则不需要扩容。

如果minCapacity大于elementData.length则需要扩容,调用grow(minCapacity)方法。

参数为新数组的长度。

//数组最大容量,-8是因为有些VM会在数组中保留一些词
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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);
}

这个grow方法就是扩容的关键所在了。

扩容后的数组长度的关键就是这一行代码

int newCapacity = oldCapacity + (oldCapacity >> 1);

(oldCapacity >> 1)就是右移运算表示除以2

假设现在oldCapacity为10,其原码为1010,向右移动一位就是0101,也是5。

所以扩容后的新容量newCapacity就是15。

接着判断扩容后的容量newCapacity和新数组的容量minCapacity进行比较。

  1. 如果小于,也就是扩容后的容量newCapacity还不够,那么将新数组的长度minCapacity赋值给newCapacity

然后将扩容容量newCapacity和数组最大容量MAX_ARRAY_SIZE对比

  1. 如果大于,则执行hugeCapacity(minCapacity)方法
//数组最大容量,-8是因为有些VM会在数组中保留一些词
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

hugeCapacity(minCapacity)方法判断如果最小扩展要求值小于0则抛出异常,否则进行判断最小扩展要求值minCapacity是否大于MAX_ARRAY_SIZE,如果大于则返回最大int值,如果不大于则返回MAX_ARRAY_SIZE值。

最后通过Arrays.copyOf(elementData, newCapacity);扩容就完成了。


4. ArrayList.addAll()源码分析

先准备一段测试代码。

public class TestArrayList {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
    }
}

下面是addAll的源码

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

addAll主要的逻辑就是将传进来的集合转化为数组,拿到这个数组的长度。

接着对当前ArrayList执行检查扩容操作,然后System.arraycopy方法将System.arraycopy数组附加到elementData数组的size下标后,然后设置size的大小,最后根据传入的容器长度来返回添加状态。


5. 总结

  1. ArrayList扩容通常为原数组大小的1.5倍,并且ArrayList最大容量为int最大值。
  2. addAll(Collection c)没有元素时候,扩容为Math.max(10, 实际元素个数),有元素时为Math.max(原来容量的1.5倍,实际元素个数)
  3. add(Object o)首次扩容为10,再次扩容为上次容量的1.5倍。
  4. ArrayList(int initialCapacity)会使用指定容量的数组
  5. ArrayList(Collection<? extends E> c)会使用c的大小作为数组容量

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

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

相关文章

禾川HCQ+X3E ModBUS 电机远程启动

前面讲过了 通过EtherCat总线级联X3E控制伺服电机,这次使用ModBus总线远程控制该电机启停。 硬件: HCQ0 1100/1200D X3EB 驱动 SV系列电机,主机电脑或者主PLC,硬件连接:电脑网口连HCQ0 port1 port2 连X3EB,软件需要用到TCP调试工具。 步骤一通讯: 1建立工程,修改本机地…

1月31日 : 读书笔记

为了让操作系统能够使用32位模式&#xff0c;需要对CPU做各种设定 最近的操作系统能同时运行多个程序&#xff0c;如果内存地址的使用范围重叠了怎么办&#xff1f;解决这个问题的方法就是分段。 什么是分段&#xff1f; 打个比方&#xff0c;将4GB的内存分成很多块&#xff0c…

【Mysql第五期 排序与分页】

文章目录案例使用的数据脚本1. 排序数据1.1 排序规则1.2 单列排序1.3 多列排序2.分页2.1 需求2.2 实现规则3.课后习题扩展分析原因问题解决总结案例使用的数据脚本 1.mysql脚本下载链接https://download.csdn.net/download/qq_43674360/87408079 2.或者自己新建一个sql后缀文本…

京东数据分析(竞品监控):飞利浦王牌产品在中国失利

近日&#xff0c;飞利浦集团发布了2022年第四季度及全年的业绩报告。根据报告显示&#xff0c;第四季度集团销售额达54亿欧元&#xff0c;可比销售额增长3%&#xff0c;可比订单量减少8%。 而全年业绩数据显示&#xff0c;集团销售额为178亿欧元&#xff0c;可比销售额下降3%&a…

sql进阶,多表及关联

–odps sql –– –author:宋文理 –create time:2023-02-01 16:24:24 –– – 创建非分区表 CREATE TABLE csxx_ffq( rq STRING COMMENT ‘日期’, xh BIGINT COMMENT ‘序号’, sj STRING COMMENT ‘数据’ ) COMMENT ‘测试数据&#xff08;非分区表)’; – 创建分区表 CRE…

ModuleNotFoundError: No module named ‘jnius‘

在termux中安了 ubuntu22.04 &#xff0c;在其中中使用apt install python3-pip 后运行pip3出错 Traceback (most recent call last):File "/usr/lib/python3/dist-packages/pip/_vendor/platformdirs/android.py", line 85, in _android_folderfrom jnius import au…

call,apply,bind的使用及原理

call,apply,bind的使用方法 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"…

StarRocks市场渗透率跻身Top10!

近日&#xff0c;国内著名的研究咨询机构艾瑞咨询发布了《2022年中国数据库研究报告》&#xff08;以下简称“报告”&#xff09;。报告指出&#xff0c;目前数据库产品的国内渗透率达到前所未有的高度&#xff0c;且头部效应明显。其中&#xff0c;StarRocks以“极速统一”的性…

java常用类:日期类Date,Calender和LocalDateTime

java常用类型: Ineteger等包装类 String类&#xff0c;StringBuffer类和StringBuilder类 Math类及常用方法 System类及常用方法 Arrays类及常用方法 BigInteger类和BigDecimal类及常用方法 日期类Date类,Calender类和LocalDateTime类 文章目录引言日期类Date(第一代)时间戳转字…

缓存更新策略分析

缓存常用于读多写少的场景&#xff0c;用于缓存结果数据&#xff0c;降低响应时间&#xff0c;提高服务性能。通常缓存与数据库一起使用&#xff0c;数据库负责持久化&#xff0c;缓存负责高性能。数据库无法同时满足持久化与高性能&#xff0c;所以引入缓存解决高性能问题。缓…

SwiftUI 文本框TextField添加清除按钮

这里写自定义目录标题前言/背景实现参考前言/背景 使用SwiftUI框架&#xff0c;希望在文本框TextField控件中输入内容后显示一个清除按钮,可以清空内容,像这样&#xff1a; UIKit 框架的 UITextField可以配置clearButtonMode,但是SwiftUI框架里的TextField没有这个&#xff…

javaScript常用语法

一、数据类型1. 原始数据类型(7个)number, string, boolean, undefined, null, symbol, bigint1.1 number包括以下三种浮点和整型数字(如3.1416926和3)NaN(not a number)不是数字infinity超出js数字范围的数值2. 引用数据类型(3个)object, array, function2.1 特殊类型RegExp, …

MySQL Performance Schema知识点

MySQL Performance Schema知识点 程序插桩&#xff08;instrument&#xff09;。程序插桩在MySQL代码中插入探测代码&#xff0c;以获取我们想了解的信息。 消费者表&#xff08;consumer&#xff09;&#xff0c;指的是存储关于程序插桩代码信息的表。如果我们为查询模块添加…

基于Springboot搭建java项目(二十六)——创建Vue前端项目

创建Vue前端项目 一、创建Vue前端项目 1、安装 Vue CLI 1.1、下载Node.js 因为需要使用 npm 安装 Vue CLI&#xff0c;而 npm 是集成在 Node.js 中的&#xff0c;所以第一步我们需要安装 Node.js&#xff0c;访问官网 https://nodejs.org/en/&#xff0c;首页即可下载。 下…

day26|455.分发饼干、376. 摆动序列、53. 最大子序和。进军贪心

455.分发饼干 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都有…

Python | 数据类型之元组和字典

知识目录一、元组(tuple)1.1 元组的创建1.2 访问和修改元组1.3 内置函数二、字典(dict)2.1 字典的创建2.2 访问和修改字典2.3 字典键的特性2.4 函数与方法一、元组(tuple) Python 的元组与列表类似&#xff0c;不同之处在于元组的元素不能修改。 元组使用小括号&#xff0c;列…

kubernetes-dashboard 实现 http 访问以及免 token 登录

文章目录[toc]下载官方 yaml 文件修改 yaml 文件修改 service 端口修改 clusterrolebinding修改 deployment 内容修改探针检测修改镜像拉取策略修改容器端口关闭 token 登录增加 ingress完整版 yaml下载官方 yaml 文件 最后有完整版的 yaml 文件&#xff0c;不想看细节的话&am…

苹果中的这些小技巧,你知道吗

技巧一&#xff1a;iPhone镜像 觉得手机屏幕太小看电影玩游戏不爽怎么办&#xff1f;投屏功能帮我们解决了问题&#xff0c;使用方法也很简单。打开控制中心&#xff0c;点击投屏&#xff0c;选择设备&#xff0c;连接投屏。这里需要注意的是&#xff0c;手机和连接的设备必须在…

【MIKE水动力】MIKE11基本原理(上)

Mike11软件包由水动力、对流&#xff5e;扩散、水质、降雨&#xff5e;径流、洪水预报等模块组成&#xff0c;核心模块为水动力模块。Mike11水动力模块采用6点Abbott&#xff5e;Ionescu有限差分格式对圣维南方程组求解。 一、圣维南方程组 1、基本要素与假设条件 Mike11模型…

开源流程引擎activiti、flowable、camunda选哪个好?

市场上比较有名的开源流程引擎有osworkflow、jbpm、activiti、flowable、camunda。其中&#xff1a;Jbpm4、Activiti、Flowable、camunda四个框架同宗同源&#xff0c;祖先都是Jbpm4&#xff0c;开发者只要用过其中一个框架&#xff0c;基本上就会用其它三个。低代码平台、办公…