ArrayList默认容量10是懒加载的,构造时不分配空间,首次add才扩容;扩容约1.5倍并数组拷贝,remove(index)为O(n),线程不安全。

底层用的是数组,但不是“一创建就分配10个空间”
很多人以为 new ArrayList() 一执行,内存里就立刻划出10个 Object 槽位——这是错的。真实情况是:无参构造器只把 elementData 指向一个共享的空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时容量为 0,size 也为 0。
真正分配默认 10 个空间,发生在第一次调用 add() 时:ensureCapacityInternal() 检测到当前数组是空的,就把最小需要容量设为 Math.max(10, minCapacity)(即至少 10),然后触发扩容。
- 所以“默认容量10”是**懒加载的**,不是构造即分配
- 如果你明确知道要存 1000 个元素,直接用
new ArrayList(1000),能省掉 5~6 次扩容复制 - 扩容不是翻倍,而是
oldCapacity + (oldCapacity >> 1)→ 约 1.5 倍(Java 7+)
扩容本质是数组拷贝,Arrays.copyOf() 是性能关键点
每次扩容都要新建数组、再把老数组所有元素逐个复制过去。这个过程由 grow() 方法完成,核心就是这行:
elementData = Arrays.copyOf(elementData, newCapacity);
Arrays.copyOf() 底层调用的是 System.arraycopy(),属于 JVM 内建的高效内存拷贝,但仍是 O(n) 操作。频繁扩容会明显拖慢批量添加性能。
立即学习“Java免费学习笔记(深入)”;
- 插入第 11 个元素时:从容量 10 → 15,拷贝 10 个对象
- 插入第 1001 个元素时:若初始容量仍为 10,已发生约 8 次扩容,累计拷贝超 3000 次引用
- 用
ensureCapacity(int minCapacity)预留空间,可完全避免中间扩容
get() 和 set() 是 O(1),但 remove(int index) 是 O(n)
因为底层是连续数组,get(int index) 直接算内存偏移,快得没商量;set() 同理,只是覆盖引用。
但 remove(int index) 不同:删掉中间某个元素后,它后面所有元素都得往前挪一位。JVM 会用 System.arraycopy() 把 [index+1, size) 这段整体左移。
- 删末尾(
remove(size-1))最快:不移动任何元素 - 删开头(
remove(0))最慢:移动size-1个引用 - 如果要批量删多个元素,别反复调
remove(),改用迭代器remove()或先记下索引再倒序删
线程不安全不是“警告”,是真会静默出错
ArrayList 没有任何同步机制,modCount 字段仅用于快速失败(fail-fast)检测,不是锁。
两个线程同时 add(),可能触发以下任一结果:
- 一个线程的元素被另一个覆盖(
elementData[i] = e竞态) - 扩容时两个线程都判断需扩容,各自新建数组并复制,最终只有一个生效,另一个丢失全部数据
-
size++不是原子操作,可能导致size少加,后续get()越界或漏读
别信“我只读不写就没事”——即使全是 get(),如果另一线程正在扩容并重置 elementData,你可能拿到 null 引用或旧数组残留垃圾值。
真要并发写,用 CopyOnWriteArrayList(适合读多写少)或外层加 synchronized 块,而不是自己手写双重检查。










