【Java集合】ArrayList :底层原理、数组互转与扩容计算

以下是 ArrayList 的底层原理的详细介绍,结合其动态数组实现、扩容机制、核心操作及线程安全性等方面展开:

一、底层结构与初始化

ArrayList 的底层基于 Object 数组 **elementData** 实现 ,通过动态扩容机制 支持元素的动态增删。

• 核心属性:

elementData:存储元素的数组,初始为空数组(无参构造时)。

size:记录当前元素数量(非数组长度)。

• 初始容量: 无参构造默认空数组JDK7 是 10,JDK8 优化为懒加载,在空参构造时创建一个ArrayList时0,首次 add 时扩容至 10 );

• 可通过构造器指定初始容量(如 new ArrayList<>(50)),避免频繁扩容。

• 扩容本质:通过Arrays.copyOf()复制原数组到新数组,耗时且产生内存碎片,因此批量添加建议先用ensureCapacity()预扩容;

二、动态扩容机制

  1. 扩容规则:

• 初始容量: 无参构造默认空数组JDK7 是 10,JDK8 优化为懒加载,在空参构造时创建一个ArrayList时0,首次 add 时扩容至 10);

• 触发时机:添加元素时,若 size + 1 > elementData.length,则调用 grow() 扩容;

• 新容量 = 旧容量 × 1.5**(通过** **oldCapacity + (oldCapacity >> 1)** 实现右移运算 0.5倍)

• 若一次性添加多个元素(如 addAll()),直接扩容至 Math.max(原容量 × 1.5, 实际所需容量)

  1. 扩容实现:
    • 调用 Arrays.copyOf() 将旧数组数据复制到新数组 ,旧数组被 GC 回收。
    • 最大容量限制为 Integer.MAX_VALUE - 8(避免内存溢出)。

代码debug调试:

java 复制代码
@Test
public void t1() {
    //初始化容量:0
    ArrayList<Integer> list = new ArrayList<>();
    //第一次初始化:初始化容量为 10
    list.add(1);
    //当已经使用的数组长度大于当前数组长度(10)
    //第11次添加:调用grow方法扩容 1.5倍  变10x1.5=15
    for(int i = 1;i<10;i++){
        list.add(i+1);
    }
    list.add(100);
}

三、核心操作的时间复杂度

操作 时间复杂度 说明
尾部追加元素 O(1) 均摊 扩容时需 O(n),但均摊后为 O(1)。
随机访问 O(1) 直接通过数组索引访问(如 get(index))。
插入/删除元素 O(n) 需移动后续元素(如 add(index, e)remove(index))。
遍历 O(n) 顺序访问数组元素。

关于查找的时间复杂度

  • 下标查找:O(1) 直接返回
  • 未排序查找:O(n) 遍历
  • 排序查找:O(logn) 二分

四、数组获取元素地址原理

a[i]=数组首地址+下标(索引)*数组元素类型长度

关于为什么不从1开始?(多一次减法操作

五、线程安全性与替代方案

• 非线程安全

• 多线程并发修改会导致数据不一致或 ConcurrentModificationException(通过 modCount 检测修改次数)。

解决方案:

• 使用 Collections.synchronizedList(new ArrayList<>()) 包装为同步列表。

• 高并发读场景下,采用 CopyOnWriteArrayList(写时复制,牺牲一致性)。

六、与 Vector 的对比

特性 ArrayList Vector
线程安全性 非线程安全 线程安全(方法用 synchronized修饰)。
扩容机制 扩容 1.5 倍 扩容 2 倍。
性能 更高(无锁开销) 较低(锁竞争)。
初始容量 默认 10(延迟初始化) 默认 10(直接初始化)。

七、数组与 List 互转的坑点

1. Arrays.asList() 数组转 List

  • 核心特性 :返回的是 Arrays 内部类 ArrayList(不是 java.util.ArrayList),底层直接引用原数组,共享同一块内存地址。

  • 坑点

    • 修改原数组元素 → List 内容同步改变(受影响);
    • 不能调用 add/remove 方法,会抛 UnsupportedOperationException(内部类未实现增删)。
  • 正确用法new ArrayList<>(Arrays.asList(strs)) 包装成真正的 java.util.ArrayList,切断与原数组的关联。

2. List.toArray() List 转数组

  • 核心特性 :底层会拷贝一份新数组,与原 List 完全隔离。

  • 坑点

    • 修改 List 元素 → 数组内容不会改变(不受影响);
    • 推荐写法:list.toArray(new String[list.size()]),避免无参 toArray() 返回 Object[] 强转风险。

八、源码关键方法

  1. 添加元素(add(e)):
java 复制代码
public boolean add(E e) {     
    ensureCapacityInternal(size + 1); // 检查扩容
    elementData[size++] = e; // 尾部插入
    return true;
}
  • 检查容量:判断 size + 1 是否超过当前数组长度,不足则先扩容;
  • 赋值:将新元素放到 elementData[size] 位置,size 自增 1;
  • 返回:返回 true 表示添加成功。

ensureCapacityInternal() 确保容量足够,触发扩容逻辑。

  1. 扩容(grow()):
java 复制代码
private void grow(int minCapacity) {  
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
    elementData = Arrays.copyOf(elementData, newCapacity); // 复制数组
}

面试必会

new ArrayList(10) 扩容几次

  • 答案0 次,不会扩容。
  • 原理
  • 调用有参构造时,直接创建 new Object[10] 赋值给 elementData
  • 只有当 size + 1 > elementData.length 时才会触发 grow(),初始容量 10 足够容纳前 10 个元素,因此前 10 次添加都不会扩容。
  • 延伸
  • 第 11 次添加时,size + 1 = 11 > 10,触发第一次扩容到 15;
  • 第 16 次添加时,触发第二次扩容到 22(15 + 15>>1 = 22)。

面试官:ArrayList 底层原理是什么?

答:

  • ArrayList 底层是动态 Object 数组

  • 初始化容量:JDK7 是 10,JDK8 优化为懒加载,ArrayList初始容量为0,首次 add 时扩容至 10

  • 有参构造 new ArrayList(10) 直接创建长度 10 的数组,不会扩容。

  • 扩容:扩容到原来容量的1.5倍,每次扩容需要数组拷贝

  • 添加元素:

    • 检查容量:判断 size + 1 是否超过当前数组长度,不足则先扩容;
    • 赋值:将新元素放到 elementData[size] 位置,size 自增 1;
    • 返回:返回 true 表示添加成功。
  • 非线程安全。

面试官:Arrays.asList 转 List 后改数组,List 会变吗?

答:会变,因为 Arrays.asList 返回的内部类 ArrayList 直接引用原数组,共享内存;而 List.toArray 会拷贝新数组,改 List 不影响数组。

面试官:new ArrayList (10) 扩容几次?

答:0 次,因为构造时直接创建了长度为 10 的数组,前 10 次添加都不会触发扩容。

相关推荐
寻见9032 小时前
10 分钟吃透 MyBatis 核心|从底层原理到实战技巧,Java 开发者必藏(无废话干货)
java·mysql·mybatis
泰式大师2 小时前
别再让 Agent 靠感觉改计划了:我把 Replan 做成了一个可计数的系统事件
后端
颜酱2 小时前
理解并查集Union-Find:从原理到练习
javascript·后端·算法
隔壁小邓2 小时前
分布式事务
java·后端
我叫黑大帅2 小时前
如何让两个Go程序远程调用?
后端·面试·go
玛卡巴卡ldf2 小时前
【LeetCode 手撕算法】(双指针) 1-两数之和、283-移动零、11-盛最多水的容器、15-三数之和
数据结构·算法·leetcode
OxyTheCrack2 小时前
简述各语言GC(垃圾回收)机制
开发语言
李昊哲小课2 小时前
电商系统项目教程
开发语言·前端·javascript
末点2 小时前
超长文本格式坐标串数据空间化入库
数据库·c#·st_geomfromtext