ArrayList扩容机制

简介: ArrayList的add方法添加元素时,先调用ensureCapacityInternal()确保容量。首次添加时,最小容量设为10,触发扩容;后续添加若超出当前容量,则调用grow()方法,将容量扩为原来的1.5倍。grow通过位移运算高效计算新容量,并复制元素到新数组。length是数组属性,length()是字符串方法,size()用于集合元素计数。

先来看Add方法
再来看看ensureCapacityInternal()方法,可以看到add()方法首先调用了ensureCapacityInternal(size+1)
当要add进第一个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity为10
ensureExplicitCapacity()方法
我们来仔细分析一下
当我们要add进第一个元素到ArrayList时,elementData.length为0(因为还是一个空的list,里面还没有数据,所以没有进行扩容,默认扩容10),因为执行了ensureCapacityInternal()方法,所以minCapacity此时为10。此时,minCapacity - elemetData.length > 0(minCapacity=10,elemetData.length=0)成立,所以会进入==grow(minCapacity)==方法。
当add第2个元素时,minCapacity为2,此时elementData.length(容量)在添加第一个元素后扩容成10了。此时,minCapacity - elementData.length > 0不成立,所以不会进入(执行)==grow(minCapacity)==方法。
添加第3、4…到第10个元素时,依然不会执行==grow()==方法,数组容量都为10。
知道添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进行grow方法进行扩容
grow方法
Java
运行代码
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() 方法是针对泛型集合说的,如果想看这个泛型有多少元素,就调用此方法类查看!

相关文章
|
存储 安全 Java
BlockingQueue(阻塞队列)基本使用指南
BlockingQueue(阻塞队列)基本使用指南
615 1
|
5月前
|
Java
ArrayList 的扩容机制解析
ArrayList扩容机制解析:添加元素时先检查容量,不足则触发扩容。默认初始容量为10,每次扩容1.5倍,通过数组拷贝实现,耗时O(n)。频繁扩容影响性能,建议预估容量并初始化指定大小,提升效率。
|
5月前
|
存储 安全 Java
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
413 4
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
463 1
|
并行计算 算法 Java
Java中的Fork/Join框架详解
Fork/Join框架是Java并行计算的强大工具,尤其适用于需要将任务分解为子任务的场景。通过正确使用Fork/Join框架,可以显著提升应用程序的性能和响应速度。在实际应用中,应结合具体需求选择合适的任务拆分策略,以最大化并行计算的效率。
444 23
|
算法 Java 数据库
理解CAS算法原理
CAS(Compare and Swap,比较并交换)是一种无锁算法,用于实现多线程环境下的原子操作。它通过比较内存中的值与预期值是否相同来决定是否进行更新。JDK 5引入了基于CAS的乐观锁机制,替代了传统的synchronized独占锁,提升了并发性能。然而,CAS存在ABA问题、循环时间长开销大和只能保证单个共享变量原子性等缺点。为解决这些问题,可以使用版本号机制、合并多个变量或引入pause指令优化CPU执行效率。CAS广泛应用于JDK的原子类中,如AtomicInteger.incrementAndGet(),利用底层Unsafe库实现高效的无锁自增操作。
694 0
理解CAS算法原理
|
Java 编译器 UED
Arrays.asList() 数组转换成集合酿成的线上事故,差点要滚蛋了!
本文介绍了Java开发中使用`Arrays.asList()`方法将数组转换为集合时的一个常见陷阱。该方法返回的List是固定长度的,不支持添加或删除操作,直接使用可能导致线上故障。文章通过一次实际开发中的事故案例,分析了问题的原因,并提供了使用`java.util.ArrayList`进行封装的解决方案,以避免此类错误的发生。希望读者能从中吸取教训,提高代码的健壮性。
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
623 4
|
Java Spring
Springboot-starter的自动配置原理-及案例实现
Springboot-starter的自动配置原理-及案例实现
360 83