Java 8 ArrayList hugeCapacity 函数与 MAX_ARRAY_SIZE

简介: Java 8 ArrayList hugeCapacity 函数与 MAX_ARRAY_SIZE

1、背景

今天有一个朋友问到一个为什么 ArrayList 源码扩容方法中,数组长度最大值是 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 的问题(真的是MAX_ARRAY_SIZE? )。

在这里插入图片描述

并给出下列截图:
在这里插入图片描述

2、别急,让我们捋一捋

我们先搞清楚这里几个关键变量的含义:

  • min capacity 这次扩容最小需要的容量
  • old capacity 扩容前原始数组容量
  • newCapacity = oldCapacity + (oldCapacity >> 1) 是预计要扩容到的容量

在这里插入图片描述

之前讲过,看源码看不太懂时要多看注释。

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

这里说 Some VMs reserve some header words in an array. 即有些虚拟机会在数组中保存 header words 头部字。

对象头可以看这里:
https://cloud.tencent.com/developer/article/1413543
https://stackoverflow.com/questions/26357186/what-is-in-java-object-header

PS: 数组有点特殊性,数组对象要额外存储数组元素长度在头部,少了这8个长度可能与此有关。

尝试分配大于 MAX_ARRAY_SIZE 长度的数组会导致 OOM (换句话说,超过了该虚拟机的数组长度限制)。

grow 函数的目的是:提高容量以便至少满足最少 minCapacity 容量!!

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

有些虚拟机大于 MAX_ARRAY_SIZE (Integer.MAX -8 )就容易OOM (注意只是有些)

注意前提是 new - MAX_ARRAY_SIZE >0 就意味着 正常情况下新的扩容长度大于了 MAX_ARRAY_SIZE。
此时最大可以扩容到 Integer.MAX,因为数组长度是整数。

在这里插入图片描述

因为数组理论上长度就是 Integer.MAX_VALUE 个别JVM 设计上的问题 咱们可以尽量照顾下 但并不是说一定因为个别JVM 就一定不让扩容到 整数最大值长度。

如果再满了 那么对不起 直接到将数组长度设置为整数最大值, 爱咋咋地!

因此,数组最大容量是 Integer.MAX_VALUE (提问的说法有问题) ,在图示情况扩容到 MAX_ARRAY_SIZE 是为了扩容到 MAX_ARRAY_SIZE以上长度就OOM的虚拟机可以尽量不OOM,如果还放不下没办法,对不起了大兄弟!

3 Learn more

你以为这就完了吗?
其实上面的问题并不是重点。
看这这段代码最重点在:
int newCapacity = oldCapacity + (oldCapacity >> 1);

为什么不是扩容到 minCapacity +1?
其实是预先申请这么多的容量,避免频繁扩容,采用了空间换时间思想。
在这里插入图片描述

Redis 的动态字符串和这个有点类似,只不过扩容策略有差异 。

那么为什么Redis 动态字符串扩容会有两个处理方式?为什么大于1M 每次增加1M 而且有最大长度限制呢?
1 首先想下 Redis 和 Java中的ArrayList的使用场景。
Redis 通常用作缓存,而且失效时间相对较长(少则几秒钟,多则几分钟,几个小时等)。而ArrayList 通常在某个函数中用,一般来说生命周期很短,出栈后就可以回收。
2 Redis 为啥要有最大值限制。可能是因为通常和使用者不在同一个服务器上,需要通过网络进行传输,如果很大,传输很容易超时,而且Redis 主任务为单线程,很容易阻塞其他任务的执行。
3 小于1M

4、总结

之前一直提倡看源码一定要重视看注释,当大家 JDK 或者其他开源项目源码有困惑时一定要重视看源码。
同样地,当我们写代码时,有些情况容易让别人困惑时,一定要加上注释。

此外,读源码一定要像这位同学一样,多一些思考。
读源码要读源码的设计理念,体现出来的设计思想。
读源码时建议先猜想后验证,即猜想可能的原因,然后再去分析,这样学到更多。

关于如何高效阅读源码可以看我另外一篇文章。
https://blog.csdn.net/w605283073/article/details/89290798


对了,我最近在 GitChat上写了三篇不错的文章,大家感兴趣可以读读:

《性能优化方法论》
《CodeReview 的正确姿势》
《排错避坑宝典》

相关文章
|
人工智能 安全 JavaScript
Java ArrayList:动态数组
本文探讨Java中的数组,对比C/C++、JS/PHP/Python等语言的数组特性。文章分析了Java数组的定义、创建方式及其规范,指出其优缺点。Java数组作为引用类型,在堆上分配内存,支持动态大小,避免了C/C++中裸数组的常见问题(如越界访问)。然而,Java数组也存在性能瓶颈和设计缺陷,例如运行时的安全检查影响速度,无法创建超大数组或泛型数组,且多线程场景下缺乏同步机制。作者建议在实际开发中用集合替代数组以规避这些问题。
342 1
|
9月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
395 4
|
10月前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
290 4
|
10月前
|
缓存 Java 开发者
Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
435 3
|
Java 索引
Java ArrayList中的常见删除操作及方法详解。
通过这些方法,Java `ArrayList` 提供了灵活而强大的操作来处理元素的移除,这些方法能够满足不同场景下的需求。
799 30
|
人工智能 Java
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
1056 1
Java 中数组Array和列表List的转换
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
738 4
Java ArrayList扩容的原理
|
存储 安全 Java
从源码到场景,用 5 分钟讲透 Array 和 ArrayList 的差异
大家好,我是小米,29岁的技术分享者。今天聊聊社招面试中常见的问题——Array和ArrayList的区别。数组是固定大小的容器,长度不可变,性能高;ArrayList是动态数组,可自动扩容,支持更多操作但性能稍逊。在实际开发中,根据需求选择:高性能、固定大小选数组;灵活操作选ArrayList。希望这篇文章能帮你答出漂亮的答案!欢迎关注我的微信公众号“软件求生”,获取更多技术干货。
306 5
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
1126 3

热门文章

最新文章

  • 1
    PHP 数组查找:为什么 `isset()` 比 `in_array()` 快得多?
    322
  • 2
    Java 中数组Array和列表List的转换
    1056
  • 3
    JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
    773
  • 4
    通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式、聚合统计、处理树结构数据和性能优化,reduce()的使用详解(附实际应用代码)
    1607
  • 5
    通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理;js数组元素检查的方法,some()的使用详解,array.some与array.every的区别(附实际应用代码)
    719
  • 6
    通过array.every()实现数据验证、权限检查和一致性检查;js数组元素检查的方法,every()的使用详解,array.some与array.every的区别(附实际应用代码)
    515
  • 7
    多维数组操作,不要再用遍历循环foreach了!来试试数组展平的小妙招!array.flat()用法与array.flatMap() 用法及二者差异详解
    355
  • 8
    别再用双层遍历循环来做新旧数组对比,寻找新增元素了!使用array.includes和Set来提升代码可读性
    351
  • 9
    Array.forEach实战详解:简化循环与增强代码可读性;Array.forEach怎么用;面对大量数据时怎么提高Array.forEach的性能
    233
  • 10
    深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
    834