详解Java ArrayList

ArrayList 作为 Java 集合框架中最基础且常用的动态数组实现,其内部通过对数组的精细化管理,实现了动态扩容、高效元素操作等核心能力。本文将从源码层面深入拆解 ArrayList 的扩容策略、索引查询、拷贝机制、哈希计算、元素删除、批量操作等关键模块,揭示其设计逻辑与性能优化细节。

  • 动态扩容:通过 1.5 倍增长系数平衡内存与效率;
  • 高效数组操作 :依赖System.arraycopy实现元素移动与拷贝,利用 native 方法提升性能;
  • 浅拷贝策略:在数据隔离与内存复用间取得平衡;
  • fail-fast 机制 :通过modCount检测并发修改,保证数据一致性。

动态扩容

ArrayList 的扩容是其区别于普通数组的核心特性,grow(int minCapacity)方法根据数组初始化状态,采用差异化的扩容逻辑,平衡内存占用与操作效率。

ArrayList 通过grow(int minCapacity)方法实现动态扩容,根据数组初始化状态采取差异化策略:

java 复制代码
private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* 最小增长量 */
                oldCapacity >> 1           /* 首选增长量(原容量的1/2) */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}
  • 指定初始容量场景 :按原容量的 1.5 倍扩容(oldCapacity >> 1等价于除以 2),兼顾内存利用率与扩容效率;
  • 默认空数组场景 :首次扩容直接使用DEFAULT_CAPACITY(默认 10)或所需最小容量的较大值,避免频繁扩容。

索引查询

ArrayList 通过分层设计的查询方法,实现从 "存在性检查" 到 "精准索引定位" 的全场景支持,核心依赖遍历与 null 值兼容逻辑。

ArrayList 提供多层次索引查询方法,兼顾通用性与效率:

存在性检查

java 复制代码
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

首次 / 末次索引查询

java 复制代码
public int indexOf(Object o) {
    return indexOfRange(o, 0, size);
}

public int lastIndexOf(Object o) {
    return lastIndexOfRange(o, 0, size);
}

范围查询

java 复制代码
int indexOfRange(Object o, int start, int end) {
    Object[] es = elementData;
    if (o == null) {
        for (int i = start; i < end; i++) {
            if (es[i] == null) return i;
        }
    } else {
        for (int i = start; i < end; i++) {
            if (o.equals(es[i])) return i;
        }
    }
    return -1;
}
  • null 值处理:单独判断避免空指针异常;
  • 遍历策略:正序 / 逆序遍历分别适配首次 / 末次查询需求;
  • 性能特性:时间复杂度 O (n),适用于中小规模数据查询。

拷贝

ArrayList 的拷贝操作通过**clone()toArray()**实现,均采用浅拷贝策略:

java 复制代码
public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size); // 数组结构拷贝
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}

拷贝特性

  • 物理分离 :新 ArrayList 的elementData是独立数组,修改新数组结构(如 add/remove)不会影响原数组;
  • 逻辑共享 :数组元素为对象引用时,新旧数组指向同一对象实例,修改元素属性(如user.setName("new"))会双向影响;
  • 应用场景:适合快速复制数组结构,无需深拷贝元素的场景(如临时数据处理)。

哈希计算

ArrayList 的哈希计算遵循 Java 集合框架标准,通过累加策略保证顺序敏感性:

java 复制代码
int hashCodeRange(int from, int to) {
    final Object[] es = elementData;
    int hashCode = 1;
    for (int i = from; i < to; i++) {
        Object e = es[i];
        hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
    }
    return hashCode;
}

优势

  • 31 的选择
    • 31 是奇素数,素数特性减少哈希冲突(若用合数,哈希值会被因子整除,分布更集中);
    • 31 可通过移位优化计算:31 * i = (i << 5) - i(左移 5 位等价于乘以 32,减 i 即乘以 31),提升效率。
  • 顺序敏感性 :累加方式使元素顺序影响最终哈希值(如[a,b][b,a]哈希值不同),符合 List "有序集合" 的特性;
  • null 兼容:null 元素哈希值记为 0,避免空指针异常。

元素删除

ArrayList 的删除操作通过fastRemove()shiftTailOverGap()实现,核心依赖System.arraycopy优化性能:

单个元素删除

java 复制代码
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null; // 置null便于GC回收
}

范围元素删除

java 复制代码
private void shiftTailOverGap(Object[] es, int lo, int hi) {
    System.arraycopy(es, hi, es, lo, size - hi);
    for (int to = size, i = (size -= hi - lo); i < to; i++)
        es[i] = null;
}

性能优化点

  • 批量移动 :通过System.arraycopynative 方法实现高效内存块复制;
  • 空间回收:删除后置 null 操作帮助 JVM 进行垃圾回收;
  • 时间复杂度:单次删除为 O (n),批量删除通过遍历优化减少移动次数。

批量操作

ArrayList 通过**batchRemove()统一实现removeAll()retainAll()**:

java 复制代码
boolean batchRemove(Collection<?> c, boolean complement,
                    final int from, final int end) {
    Objects.requireNonNull(c);
    final Object[] es = elementData;
    int r;
    // 定位首个需要保留/删除的元素
    for (r = from;; r++) {
        if (r == end) return false;
        if (c.contains(es[r]) != complement) break;
    }
    int w = r++;
    try {
        for (Object e; r < end; r++)
            if (c.contains(e = es[r]) == complement)
                es[w++] = e; // 原地覆盖保留元素
    } catch (Throwable ex) {
        System.arraycopy(es, r, es, w, end - r);
        w += end - r;
        throw ex;
    } finally {
        modCount += end - w;
        shiftTailOverGap(es, w, end); // 清理尾部无效元素
    }
    return true;
}
  • 原地操作:通过覆盖写入减少数组复制开销;
  • 异常安全:catch 块保证异常时数据完整性;
  • 通用设计 :通过complement参数切换删除 / 保留逻辑,复用代码逻辑。
  • complement 参数
    • false(removeAll):保留c.contains(es[r]) == false的元素(删除交集);
    • true(retainAll):保留c.contains(es[r]) == true的元素(保留交集)。
  • 原地覆盖 :通过写指针w在原数组上覆盖元素,避免创建新数组,节省内存;
  • 异常安全:catch 块保证遍历过程中出现异常时,未处理元素仍能正确复制,避免数据丢失。

批量添加

ArrayList 支持尾部追加与指定位置插入两种批量添加方式:

尾部追加

java 复制代码
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0) return false;
    Object[] elementData;
    final int s;
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    System.arraycopy(a, 0, elementData, s, numNew);
    size = s + numNew;
    return true;
}

指定位置插入

java 复制代码
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    // 扩容检查...
    int numMoved = s - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index,
                         elementData, index + numNew, numMoved);
    System.arraycopy(a, 0, elementData, index, numNew);
    size = s + numNew;
    return true;
}
相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔5 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说10 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json