ArrayList适合频繁随机访问、较少增删中间元素的场景,底层为Object[]数组,get/set为O(1);非末尾增删触发O(n)复制;扩容按1.5倍增长,建议预设容量;线程不安全;泛型擦除后存Object,基本类型需注意装箱开销;trimToSize()需手动调用释放内存。

ArrayList 适合频繁随机访问、较少增删中间元素的场景
当你的业务逻辑主要涉及按索引读取、遍历、在末尾添加或删除元素时,ArrayList 是最优选。它底层是 Object[] 数组,get(int index) 和 set(int index, E element) 都是 O(1) 时间复杂度。
常见适用场景包括:
- 缓存一批配置项,后续只读不改(如
List)configs = new ArrayList(); - 批量接收 HTTP 请求参数后做统一校验(索引访问字段值)
- 作为方法返回值传递只读数据集合(配合
Collections.unmodifiableList())
但要注意:在非末尾位置调用 add(int index, E element) 或 remove(int index) 会触发数组复制,时间复杂度升至 O(n),此时应考虑 LinkedList 或重构逻辑。
扩容机制决定内存占用与性能拐点
ArrayList 默认初始容量为 10,每次扩容不是 +1,而是按 1.5 倍增长(JDK 17+),即 newCapacity = oldCapacity + (oldCapacity >> 1)。扩容需新建数组并 System.arraycopy() 复制,有明显开销。
立即学习“Java免费学习笔记(深入)”;
如果你能预估大小,务必显式指定初始容量:
ArrayListlogs = new ArrayList<>(1024); // 避免多次扩容 // 而不是默认构造:new ArrayList<>() —— 可能触发 10→15→22→33→49… 多次复制
扩容倍数在不同 JDK 版本略有差异(如 JDK 8 是 1.5x,早期版本有 2x),但核心逻辑一致:用空间换均摊插入性能。不过若长期只增不删,且容量远超实际元素数(size() ),会造成内存浪费。
线程不安全是默认行为,不是 bug
ArrayList 所有方法都不加锁,多线程环境下直接共享实例会导致 ConcurrentModificationException 或数据丢失。这不是设计缺陷,而是明确的取舍:牺牲线程安全性换取单线程极致性能。
需要并发安全时,不要简单套 Collections.synchronizedList()(仅方法级同步,复合操作仍不安全),而应根据场景选择:
- 读多写少 →
CopyOnWriteArrayList(写操作复制整个数组,适合监听器列表等低频修改场景) - 高并发读写 →
ConcurrentLinkedQueue或BlockingQueue替代(注意语义是否匹配) - 必须用 List 接口且需强一致性 → 加外部锁(如
synchronized(list))并自行保证原子性
泛型擦除后的真实存储结构不可忽视
虽然声明为 ArrayList,但运行时底层仍是 Object[],所有元素都会自动装箱为 Integer 对象。这意味着:
- 基本类型集合会产生大量包装对象,GC 压力上升(如百万级
int存入ArrayList) - 比较操作走
equals()而非 ==,null元素可能引发NullPointerException - 序列化时存储的是对象引用,不是原始字节
若性能敏感且只处理基本类型,应跳过 ArrayList,改用专用库如 IntArrayList(Trove)、LongArrayList(Eclipse Collections)或 JDK 21+ 的 SequencedCollection 配合原始特化接口(仍在演进中)。
最常被忽略的一点:ArrayList 的 trimToSize() 不会自动触发,即使你 clear() 后 size=0,内部数组仍维持原容量。手动调用才能释放内存——这点在长生命周期容器中尤其关键。










