ArrayList扩容机制

简介: 本文深入分析了ArrayList的add及扩容机制。添加元素时,先调用ensureCapacityInternal()确保容量,首次添加时默认扩容至10;添加第11个元素时触发grow()方法,容量扩为原容量1.5倍。通过源码解析,揭示了elementData、size、modCount等关键字段的作用,并对比了length、length()、size()的区别,帮助理解Java集合底层实现原理。
  • 先来看Add方法
/**
*将指定的元素追加到此列表的末尾
*/
public boolean add(E e) {
  //添加元素之前,先调用ensureCapacityInternal方法
  ensureCapacityInternal(size + 1);  // Increments modCount!!(增量modCount)
  //这里看到ArrayList添加元素的实质就相当于为数组赋值
  elementData[size++] = e;
  return true;
}
  • 再来看看ensureCapacityInternal()方法,可以看到add()方法首先调用了ensureCapacityInternal(size+1)
//得到最小扩容量
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //获取默认的容量和传入参数的较大值(第一次的较大值是DEFAULT_CAPACITY=10,minCapacity=1)
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

当要add进第一个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity为10

  • ensureExplicitCapacity()方法
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        //调用grow()方法进行扩容,调用此方法代表已经开始扩容了
        grow(minCapacity);
}

我们来仔细分析一下

  1. 当我们要add进第一个元素到ArrayList时,elementData.length为0(因为还是一个空的list,里面还没有数据,所以没有进行扩容,默认扩容10),因为执行了ensureCapacityInternal()方法,所以minCapacity此时为10。此时,minCapacity - elemetData.length > 0(minCapacity=10,elemetData.length=0)成立,所以会进入==grow(minCapacity)==方法。
  2. 当add第2个元素时,minCapacity为2,此时elementData.length(容量)在添加第一个元素后扩容成10了。此时,minCapacity - elementData.length > 0不成立,所以不会进入(执行)==grow(minCapacity)==方法。
  3. 添加第3、4…到第10个元素时,依然不会执行==grow()==方法,数组容量都为10。
    知道添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进行grow方法进行扩容
  4. grow方法
private void grow(int minCapacity) {
    // oldCapacity为旧容量,newCapacity为新容量
    int oldCapacity = elementData.length;//(0,10,15)
    //将oldCapacity右移一位,其效果相当于oldCapacity/2;
    // 我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么久把最小需要容量当作数组的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //判断新容量是否大于集合的最大容量(一般大不了)
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 给elementData从新赋值(10,15)
    elementData = Arrays.copyOf(elementData, newCapacity);
}

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!

“>>”(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源

通过例子探究一下grow()方法

  • 当add第一个元素时,oldCapacity为0,经比较后第一个if判断成立,newCapacity = minCapacity(为10)。但是第二个if判断不会成立,即newCapacity不比MAX_ARRAY_SIZE大,则不会进入hugeCapacity方法。数组容量为10,add方法中return true,size增为1。
  • 当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中rerurn,true,size增为11。
  • 以此类推…
    这里补充一点比较重要,但是容易被忽视掉的知识点:
  • java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性。
  • java中的length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法。
  • java中的size() 方法是针对泛型集合说的,如果想看这个泛型有多少元素,就调用此方法类查看!
相关文章
|
1天前
|
缓存 运维 监控
一场FullGC故障排查
本文记录了一次Java应用CPU使用率异常升高的排查过程。通过分析发现,问题根源为频繁Full GC导致CPU飙升,而Full GC是因用户上传的Excel数据被加载为大对象并长期驻留JVM内存所致。使用JProfiler分析堆内存,定位到List&lt;Map&lt;String, String&gt;&gt;结构造成内存膨胀,空间效率仅约13.4%。最终提出“治本”与“治标”两类解决方案:一是将大数据移出JVM内存,存入Redis;二是优化代码,及时清理无用字段以减小对象体积。文章总结了从监控识别、工具分析到根本解决的完整排查思路,对类似性能问题具有参考价值。(238字)
|
1天前
|
存储 缓存 负载均衡
Nacos注册中心
本文介绍Nacos的安装部署、服务注册中心整合、分级模型、负载均衡策略、权重控制、环境隔离及实例类型,详解其在微服务架构中的应用,帮助开发者掌握Nacos核心功能与最佳实践。
 Nacos注册中心
|
1天前
|
负载均衡 算法 架构师
Ribbon负载均衡
本文深入讲解Spring Cloud中Ribbon实现客户端负载均衡的原理,包括@LoadBalanced注解的作用、负载均衡策略分类与算法,以及如何自定义配置和优化首次调用延迟的饥饿加载机制,帮助读者全面理解微服务间的流量分发技术。
Ribbon负载均衡
|
1天前
|
Java Nacos Maven
Eureka服务注册与发现
本节介绍Eureka注册中心的搭建与使用,完成服务注册与发现功能,为后续Nacos替换做铺垫。
 Eureka服务注册与发现
|
2天前
|
安全 JavaScript
JeecgBoot介绍
JeecgBoot是一款基于代码生成器的低代码开发平台,支持零代码快速开发。采用SpringBoot2.x、Ant Design&Vue、Mybatis-plus等主流技术,前后端分离架构,集成Shiro、JWT安全控制,助力高效构建企业级应用。
JeecgBoot介绍
|
2天前
|
NoSQL 关系型数据库 Java
基础环境配置
项目开发环境要求JDK8+、Maven、Redis 3.2+、MySQL 5.7+,推荐使用Idea开发工具,需安装Lombok插件和JRebel热部署工具。技术栈基于SpringBoot、MybatisPlus、Shiro及SpringCloud Alibaba,适合构建微服务架构应用。
基础环境配置
|
1天前
|
数据库 前端开发 NoSQL
代码拉取与运行
本文档介绍JeecgBoot前后端项目部署流程,包含代码拉取(在线/离线)、数据库脚本导入、Idea工程配置、修改数据库与Redis连接、后端启动及前端Vue3项目运行步骤,附目录结构与关键配置说明,助您快速搭建开发环境。
代码拉取与运行
|
1天前
|
Dubbo IDE API
SpringCloud工程部署启动
本文介绍SpringCloud微服务工程搭建全过程,涵盖项目创建、模块配置、数据库部署及服务远程调用实现。通过两种方案快速搭建工程,使用RestTemplate完成服务间HTTP通信,并解析调用流程与设计思想,帮助开发者掌握微服务基础架构与协作机制。
|
1天前
|
数据采集 领域建模 数据库
领域模型图(数据架构/ER图)
本文介绍如何通过四色原型法进行领域建模,构建数据架构中的ER图。利用时标性(MI)、参与方-地点-物品(PPT)、角色(Role)和描述(DESC)四类原型,逐步从业务流程中提炼实体与关系,最终形成清晰的数据模型,助力系统设计。
|
1天前
|
uml C语言
系统时序图
时序图(Sequence Diagram)是UML中描述对象间消息传递时间顺序的交互图。横轴为对象,纵轴为时间,通过生命线、控制焦点和消息等元素展现动态协作过程,强调交互的时间先后关系,适用于建模并发与同步行为。