ArrayList扩容机制

简介: 本文深入解析ArrayList的add及扩容机制。通过源码分析,揭示其首次添加元素时默认扩容至10,后续每次扩容为原容量1.5倍的核心逻辑,并详解grow()方法如何通过位运算高效实现动态扩容,同时澄清length、length()、size()等易混淆概念。
  • 先来看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() 方法是针对泛型集合说的,如果想看这个泛型有多少元素,就调用此方法类查看!
相关文章
|
2月前
|
存储 JSON NoSQL
3-MongoDB常用命令
本文介绍MongoDB数据库操作,包括创建与删除数据库、集合的显式与隐式创建、文档的增删改查、批量操作、分页查询及排序统计等基本CRUD操作,适用于文章评论数据管理。
|
2月前
|
存储 NoSQL 关系型数据库
4-MongoDB索引知识
MongoDB索引基于B树结构,可高效支持查询,避免全表扫描。主要类型包括单字段、复合、地理空间、文本及哈希索引,适用于不同查询场景,显著提升查询性能。
|
2月前
|
监控 算法 Unix
Thread.sleep(0) 到底有什么用(读完就懂)
本文深入解析Thread.Sleep函数的工作原理,结合操作系统调度机制,揭示Sleep(1000)未必准时唤醒、Sleep(0)却能触发CPU重新竞争的真相,帮助开发者正确理解线程挂起与CPU调度的关系。
|
2月前
|
Java Maven
3. 打包
本文介绍Java项目打包部署的两种方式:一是将所有内容打包进单一JAR文件,通过Maven配置、打包命令及运行指令实现快速启动与后台运行;二是将主JAR、依赖与配置文件分离,提升灵活性与维护性,并提供端口查询与进程终止方法,便于服务管理。
3. 打包
|
2月前
|
NoSQL Java 测试技术
5-MongoDB实战演练
本文介绍某头条文章评论系统的设计与实现,基于SpringDataMongoDB构建微服务,完成评论的增删改查、按文章ID查询、分页查询及点赞功能。通过MongoTemplate优化点赞操作,提升性能,并使用索引提高查询效率,整体方案高效且可扩展。
|
2月前
|
NoSQL Linux Shell
2-MongoDB单机部署
本文详细介绍MongoDB在Windows和Linux系统中的安装、配置与启动方法,包括下载地址、版本选择、解压安装、命令行及配置文件启动方式,并介绍Shell连接、图形化工具Compass的使用,以及Linux下的服务管理与防火墙配置,附带各环境安装包下载链接。
2-MongoDB单机部署
|
2月前
|
存储 缓存 算法
零拷贝
本文探讨文件传输的性能优化,指出传统方法因频繁的上下文切换和内存拷贝导致效率低下。通过零拷贝技术可减少系统调用与数据拷贝,提升传输性能。但大文件场景下,PageCache 可能适得其反,宜采用异步IO+直接IO方案,实现高效并发处理。
|
2月前
|
存储 监控 Java
2. 整合切面,参数拦截+过滤
该类基于Spring AOP实现请求参数的前置拦截与日志记录,自动捕获Controller层请求的URL、IP、方法、参数及响应耗时,便于调试与监控,支持后续扩展如数据脱敏或存储。
|
2月前
|
存储 SQL Java
1. 整合Logback,滚动记录+多文件
`logback-spring.xml` 是 Spring Boot 项目中的日志配置文件,用于定义日志输出格式、级别、路径及滚动策略。支持控制台与文件输出,按日志类型(如 INFO、ERROR、SQL、JOB 等)分类存储,便于排查问题。通过 `LogProxy.getLogger(&quot;XXX_LOG&quot;)` 获取指定日志实例,实现精细化日志管理,适用于多环境部署与调试。
|
2月前
|
Java
@Inherited
@Inherited是Java元注解,用于修饰其他注解,使其在类继承中可被子类继承。当父类使用被@Inherited修饰的注解时,子类自动获得该注解;但接口间继承或类实现接口时不生效。