ArrayList扩容机制

一、ArrayList 基础认知

ArrayList 是非线程安全 的动态数组,底层实现为Object[]数组,它的核心优势是支持随机访问、查询效率极高,同时封装了自动化的容量管理逻辑,屏蔽了数组固定长度的弊端。

重点:JDK 7 及之后,ArrayList 采用懒加载机制,初始化时不会直接分配容量,只有真正添加元素时,才会触发容量分配。

二、什么是自动扩容?

我们先明确核心定义:自动扩容是 ArrayList 的核心能力:

  1. 初始化时创建空数组,不占用实际内存空间;
  2. 执行元素添加操作时,正式分配内存容量;
  3. 当添加元素导致现有容量不足时,底层会自动创建一个更大的新数组,将原数组元素复制到新数组中,完成扩容。

简单来说:数组长度固定不可变,ArrayList 通过「创建新数组 + 复制元素」模拟了动态扩容效果

三、ArrayList 扩容全流程(核心源码逻辑)

我们以 JDK 8 为基准,拆解扩容的每一步执行逻辑,这也是面试高频考点。

1. 扩容触发时机

执行add()/addAll()方法添加元素时,会触发容量检查 :判断条件:当前元素个数 + 1 > 数组现有容量

  • 条件不成立:直接添加元素
  • 条件成立:立即触发扩容流程

2. 核心扩容步骤

(1)懒加载初始化

无参构造创建 ArrayList:new ArrayList()底层数组:private transient Object[] elementData = {};(空数组)第一次添加元素 时,直接将容量扩容为默认值 10

(2)计算新容量

ArrayList 默认扩容规则:新容量 = 旧容量 + 旧容量 >> 1扩容为原容量的 1.5 倍 >> 1是右移一位,等价于除以 2,运算效率远高于除法。

(3)严格的容量校验

扩容不是无限的,JDK 做了双层安全校验:

  1. 如果 1.5 倍后的新容量 < 最小需要容量(如批量添加大量元素),直接以「最小需要容量」作为新容量;
  2. 如果新容量超过MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,则直接使用Integer.MAX_VALUE(2^31-1)作为最终容量。

关键疑问解答:① 为什么是Integer.MAX_VALUE - 8?部分虚拟机需要在数组对象头中存储数组长度信息,预留 8 字节空间,避免内存溢出(OOM),这是推荐安全上限 。② 为什么最终可以用Integer.MAX_VALUE-8是安全建议,不是物理上限,当容量不足时,JDK 会用 int 类型的最大值做兜底保障

(4)数组复制完成扩容

底层通过Arrays.copyOf()方法实现:

  1. 创建新容量的数组;
  2. 将原数组元素浅拷贝到新数组;
  3. elementData指向新数组,丢弃旧数组。

性能瓶颈:数组拷贝是耗时操作,频繁扩容会大幅降低 ArrayList 性能。

四、完整扩容流程总结

  1. 初始化:空数组,无容量分配;
  2. 首次添加:容量直接变为 10;
  3. 常规添加:检查容量,不足则扩容 1.5 倍;
  4. 容量校准:优先满足最小需求,再限制最大容量;
  5. 数组复制:完成扩容,替换底层数组。

五、ArrayList 扩容实战优化(开发必备)

扩容的核心损耗是数组复制 ,想要提升性能,核心思路:减少扩容次数

优化方案 1:指定初始容量(最常用)

如果提前知道元素数量,创建 ArrayList 时直接指定容量,从根源避免扩容:

复制代码
// 坏代码:未知会触发多次扩容
List<String> list = new ArrayList<>();
// 好代码:提前指定1000容量,零扩容
List<String> list = new ArrayList<>(1000);

优化方案 2:批量添加时手动扩容

添加大量元素前,手动调用ensureCapacity(int minCapacity)方法,一次性扩容到位:

复制代码
List<String> list = new ArrayList<>();
// 手动扩容,提前分配足够容量
list.ensureCapacity(1000);
// 批量添加元素,无额外扩容
for (int i = 0; i < 1000; i++) {
    list.add("测试");
}

优化方案 3:避免中间插入 / 删除

数组结构决定了中间操作会触发元素移动,结合扩容会进一步降低性能,高频增删场景建议使用 LinkedList。

相关推荐
罗西的思考3 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
lichenyang4534 小时前
Docker 学习笔记(一):为什么需要镜像、容器和仓库?
前端
kyriewen4 小时前
别再对着 TypeScript 报错发呆了:我把 10 个最常见的红色波浪线翻译成了人话
前端·javascript·typescript
IT_陈寒4 小时前
SpringBoot自动配置的坑,我的API突然就404了
前端·人工智能·后端
奇奇怪怪的5 小时前
Embedding 模型 10+ 横向评测
前端
陈广亮5 小时前
Monorepo 从 0 到 1 实操指南 2026 版:pnpm catalogs + Turborepo 2.x + changesets 全链路
前端
子兮曰5 小时前
OpenMontage 深度解剖:你的 AI 编程助手,其实是个视频工作室
前端·后端·ai编程
敲代码的鱼5 小时前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
子兮曰5 小时前
前端工具链的「Rust 化」:一场没有赢家的军备竞赛?
前端·后端·rust