深入理解ArrayList:数据结构、源码分析与常见面试题

深入理解ArrayList:数据结构、源码分析与常见面试题

引言

ArrayList 是 Java 中最常用的集合类之一,它基于动态数组实现,提供了高效的随机访问和动态扩容能力。本文将深入探讨 ArrayList 的数据结构、源码实现以及常见的面试题,帮助你更好地理解和使用它。


一、数据结构:数组

数组的下标为什么从0开始?

数组的选址地址公式为:
baseAddr + i * typeSize

其中:

  • baseAddr 是数组的起始地址。
  • i 是数组下标。
  • typeSize 是每个元素占用的字节数。

如果数组下标从1开始,公式将变为:
baseAddr + (i - 1) * typeSize

这意味着 CPU 需要额外执行一个减法操作,增加了计算开销。而下标从0开始可以避免这种额外的计算,从而提高效率。


二、ArrayList源码分析

1. 成员变量

ArrayList 的核心成员变量如下:

java 复制代码
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;

// 用于空实例的共享空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

// 用于默认大小空实例的共享空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储ArrayList元素的数组缓冲区
transient Object[] elementData; // 非私有,以简化嵌套类访问

// ArrayList的大小(包含的元素数量)
private int size;

2. 构造方法

ArrayList 提供了三种构造方法:

(1) 指定初始容量
java 复制代码
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}
(2) 默认构造方法(初始容量为10)
java 复制代码
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
(3) 通过集合初始化
java 复制代码
public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        elementData = EMPTY_ELEMENTDATA;
    }
}

3. 添加与扩容机制

ArrayList 的添加操作涉及三种情况:

  1. 第一次添加元素:将容量从0扩容到默认值10。
  2. 第2到10次添加:直接添加元素,无需扩容。
  3. 第11次及以后添加:当容量不足时,扩容为原来的1.5倍,并进行数组拷贝。

扩容的核心逻辑在 grow 方法中实现:

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

三、常见面试题

1. ArrayList底层实现的原理?

ArrayList 底层采用动态数组实现。初始化时容量为0,首次添加元素时容量会初始化为10。当容量不足时,ArrayList 会将容量扩容为原来的1.5倍,并通过数组拷贝将旧数据迁移到新数组中。

2. new ArrayList(10) 中数组扩容了几次?

new ArrayList(10) 只是实例化了一个对象,并指定初始容量为10,此时并未添加任何元素,因此不会触发扩容。

3. 如何实现数组与List之间的转换?

  • 数组转List :使用 Arrays.asList(数组对象)
  • List转数组 :使用 List.toArray(数组对象)

4. 转换后修改原内容,转换后的对象是否受影响?

  • 数组转List :受影响。因为 Arrays.asList 返回的 List 是基于原数组的视图,修改原数组会影响 List
  • List转数组 :不受影响。因为 toArray 方法会创建一个新的数组,与原 List 无关。

四、总结

ArrayList 是 Java 集合框架中非常重要的一个类,它的动态数组实现使其在随机访问和动态扩容方面表现出色。通过本文的分析,我们了解了 ArrayList 的核心实现原理、扩容机制以及常见的面试题。掌握这些知识,不仅可以帮助你更好地使用 ArrayList,还能在面试中游刃有余。

希望本文对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论。


参考文献

相关推荐
人间凡尔赛4 分钟前
eBPF + WebAssembly 正在重写服务网格数据平面:2026 云原生架构的“去 Sidecar“革命
后端·云原生·架构
啊哈一半醒10 分钟前
Go 语言 Context 全方位详解:原理、实战与避坑
开发语言·后端·golang
站大爷IP18 分钟前
被 `@staticmethod` 和 `@classmethod` 坑惨了:继承链里它们真的会“变脸”
后端
orient21 分钟前
CompletableFuture 源码深度解析:6 大 API + 链式编排实战
后端
Zane199431 分钟前
map 比推导式快"是真的吗?一文讲透 map、filter、reduce 与 lambda 的真实性能与设计取舍
后端·python
字节跳动数据库37 分钟前
火山引擎 RDS MySQL 向量索引:把高性能向量检索带到 MySQL 上
人工智能·后端·mysql
拖孩44 分钟前
用 AI 重解千年观音灵签,做了一个微信小程序,每天摇一摇,命运给你回应
前端·后端·微信小程序
Python私教1 小时前
AI Agent 可观测性不只是日志:一套可回放的多步执行链
人工智能·后端·python
Python私教1 小时前
模型越强越不需要 Skills?我把 AI 编程能力拆成 4 层
人工智能·后端·python
名字还没想好☜2 小时前
Go 的 io.Reader/Writer 组合实战:io.Copy、TeeReader、MultiWriter 优雅处理数据流
开发语言·后端·golang·go·iphone