ArrayList扩容机制

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: ArrayList添加元素时,先调用ensureCapacityInternal()确保容量,首次添加时默认扩容至10。add方法实质是为数组赋值。ensureExplicitCapacity()判断是否需扩容,当容量不足时调用grow()。grow()将容量扩大1.5倍(old + (old >> 1)),并通过Arrays.copyOf()完成数组复制。size()用于集合元素计数,length为数组属性,length()为字符串方法。

● 先来看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月前
|
数据安全/隐私保护
RBAC权限模型
RBAC(基于角色的访问控制)通过角色管理权限,实现用户与权限的间接关联,提升系统安全性与管理效率。其三大原则:最小权限、职责分离、数据抽象,使权限分配更清晰、灵活,广泛应用于现代权限管理系统中。
|
2月前
|
SpringCloudAlibaba Java Nacos
SpringCloud Alibaba诞生
阿里基于Spring Cloud打造Alibaba生态,推出Nacos、Sentinel、Seata等核心组件,覆盖服务发现、配置管理、流量控制与分布式事务,形成完整微服务解决方案,获Spring官方认可,推动Spring Cloud在企业级场景高效落地。
|
2月前
|
存储
初始化Map大小并非用多少指定多少
初始化HashMap时,指定容量并非直接生效,而是会调整为最近的2的幂次(如1变2,7变8)。为避免扩容开销,建议使用Guava工具Maps.newHashMapWithExpectedSize(),或手动按公式:容量 = 预期元素数 / 0.75 + 1 设置。
|
2月前
|
NoSQL MongoDB
删除文档
MongoDB中删除文档使用db.集合名称.remove(条件)语法。如:db.comment.remove({})可清空全部数据,慎用;删除指定_id记录则用db.comment.remove({_id:&quot;1&quot;})。
|
2月前
|
存储 缓存 SpringCloudAlibaba
什么是微服务
自2014年起,微服务架构由Martin Fowler等人推动发展,主张将单体应用拆分为多个独立、轻量级的小型服务,通过RESTful API通信,围绕业务构建,支持独立开发、部署与扩展。其具备服务自治、面向服务、单一职责等特征,是历经架构演进形成的成熟分布式方案,催生了SpringCloud等主流技术生态。
|
2月前
|
监控 算法 Unix
Thread.sleep(0) 到底有什么用
Thread.Sleep(0)并非无用,它会触发操作系统立即重新进行CPU竞争,让其他线程获得执行机会,避免界面假死。而Sleep(1000)也不保证精确唤醒时间,因线程需等待调度。本文深入解析Windows抢占式调度机制,揭示Sleep背后的真实行为。
|
2月前
|
Java 调度
线程池初探
线程池通过复用线程提升性能,避免频繁创建销毁的开销。它统一管理线程,减少资源消耗与上下文切换,简化多线程编程。使用时只需提交任务,无需关注线程生命周期,支持定时、周期性任务调度,是高效稳定的并发编程工具。(238字)
泛型在静态方法和静态类中的问题
泛型类中静态成员不能使用类的泛型参数,因静态成员不依赖对象实例,而泛型类型在对象创建时才确定。如`public class Test2&lt;T&gt;`中,`static T one`会编译错误。但可定义泛型方法,如`public static &lt;T&gt; T show(T one)`,其T为方法局部泛型,独立于类泛型,合法有效。
|
2月前
|
Java 编译器
泛型擦除与多态的冲突与解决方法
泛型类 `Pair&lt;T&gt;` 在类型擦除后,泛型参数变为 `Object`,导致子类 `DateInter` 重写 `setValue(Date)` 和 `getValue()` 时实际为桥接方法实现。尽管看似重写,实则编译器生成桥接方法以兼容多态,虚拟机通过方法签名(参数与返回类型)区分,实现泛型多态的“伪重写”。
|
2月前
|
安全 编译器
自动类型转换
由于类型擦除,泛型在运行时会被替换为原始类型,但编译器会在获取泛型对象时自动插入强制类型转换。如ArrayList的get方法中,`(E) elementData[index]`会在编译时转为对应类型的强转,如`(Date)`,因此无需手动转换。同理,访问泛型字段时也会自动插入类型转换,保证类型安全。