ArrayList是基于Object[]数组实现的动态数组,随机访问O(1),中间插入/删除最坏O(n),扩容为1.5倍且手动序列化有效元素,非线程安全。

ArrayList底层用Object[]数组存储元素
ArrayList不是链表,也不是哈希表,它本质就是一个封装好的动态数组。内部核心字段是transient Object[] elementData,所有元素都连续存放在这个数组里。这意味着随机访问(get(int index))是O(1),但插入/删除中间位置元素要移动后续数据,最坏O(n)。
注意transient修饰——这个数组不会被默认序列化,ArrayList自己实现了writeObject和readObject来只序列化实际有效元素(size个),避免把扩容预留的空位也写出去。
扩容机制:1.5倍增长,但有整数溢出风险
当添加元素发现size == elementData.length时触发扩容。新容量计算逻辑是:oldCapacity + (oldCapacity >> 1),也就是1.5倍。比如原数组长度10,扩容后变成15;原长16,变成24。
但这里有两个关键细节:
立即学习“Java免费学习笔记(深入)”;
- 扩容前会先调用
Arrays.copyOf(elementData, newCapacity),底层是System.arraycopy,效率高但仍是开销 - 如果原始容量极大(比如接近
Integer.MAX_VALUE),1.5倍可能溢出变负数,此时会退回到Integer.MAX_VALUE或抛OutOfMemoryError - 首次添加元素时,如果没指定初始容量,
elementData是空的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,第一次扩容直接设为10
add()、remove()、set()这些操作为什么有时快有时慢
性能差异全看下标位置:
-
add(E e)(尾部添加):均摊O(1),仅在扩容时跳变到O(n) -
add(int index, E element)(指定位置插入):先检查index范围,再用System.arraycopy把[index, size)段整体后移一位,所以O(n−index),越靠前越慢 -
remove(int index):同理,把[index+1, size)前移,O(n−index) -
set(int index, E element):直接数组赋值,O(1),但会检查index是否>= size,越界抛IndexOutOfBoundsException
特别注意:remove(Object o)是遍历查找再删除第一个匹配项,时间复杂度O(n),不是按索引删的那个。
并发场景下直接用ArrayList会出什么问题
ArrayList完全不保证线程安全。多个线程同时add(),可能触发扩容+复制+更新size三步,而size++不是原子操作,会导致元素丢失、数组越界、甚至ArrayStoreException。
常见错误现象包括:
- 迭代时抛
ConcurrentModificationException(fail-fast机制检测到modCount != expectedModCount) - 明明add了1000次,最后
size()却小于1000 -
get(0)返回null,但size()显示大于0
真要并发写,别用Collections.synchronizedList(new ArrayList())这种粗粒度锁,优先考虑CopyOnWriteArrayList(适合读多写少)或根据场景改用ConcurrentHashMap模拟索引映射。










