详解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;
}
相关推荐
长安第一美人4 小时前
C 语言可变参数(...)实战:从 logger_print 到通用日志函数
c语言·开发语言·嵌入式硬件·日志·工业应用开发
Larry_Yanan4 小时前
Qt多进程(一)进程间通信概括
开发语言·c++·qt·学习
superman超哥5 小时前
仓颉语言中基本数据类型的深度剖析与工程实践
c语言·开发语言·python·算法·仓颉
不爱吃糖的程序媛5 小时前
Ascend C开发工具包(asc-devkit)技术解读
c语言·开发语言
bu_shuo5 小时前
MATLAB奔溃记录
开发语言·matlab
韩立学长5 小时前
【开题答辩实录分享】以《自助游网站的设计与实现》为例进行选题答辩实录分享
java·mysql·spring
ss2735 小时前
线程池:任务队列、工作线程与生命周期管理
java·后端
不像程序员的程序媛5 小时前
Spring的cacheEvict
java·后端·spring
SAP小崔说事儿5 小时前
在数据库中将字符串拆分成表单(SQL和HANA版本)
java·数据库·sql·sap·hana·字符串拆分·无锡sap
凌云若寒5 小时前
半导体代加工企业标签模板痛点的全景式解决方案
java