arraylist底层使用object[]数组实现动态扩容,当add时size等于数组长度即触发扩容,新容量为原容量1.5倍(oldcapacity + oldcapacity>>1),通过arrays.copyof复制数据并替换引用,线程不安全。

ArrayList 底层确实用的是原生数组(Object[]),但它的“动态”体现在:当数组空间不够时,会自动创建一个更大的新数组,并把旧数据复制过去——这个过程叫扩容。
扩容触发时机
每次调用 add(E e) 方法添加元素前,ArrayList 会检查当前 size 是否已等于底层数组的长度(即 elementData.length)。如果相等,说明没空位了,就会触发扩容。
- 比如初始容量为10的 ArrayList,存满10个元素后,第11次 add 就会扩容
- isEmpty()、get(int index) 等不改变 size 的操作不会触发扩容
扩容计算逻辑(JDK 8+)
扩容不是简单地 +1 或 ×2,而是按公式:newCapacity = oldCapacity + (oldCapacity >> 1),也就是扩大到原容量的 1.5 倍(右移1位等于除以2)。
- 初始容量为10 → 扩容后是15
- 15 → 22(15 + 7 = 22)
- 22 → 33(22 + 11 = 33)
- 如果计算结果小于所需最小容量(比如一次 addAll 进来很多元素),则直接取所需容量
扩容的具体步骤
扩容不是“在原数组上加空间”,而是完整替换:
- 用 Arrays.copyOf(elementData, newCapacity) 创建新数组
- 底层调用 System.arraycopy 把旧数组内容逐字节复制到新数组
- 将 elementData 引用指向新数组,旧数组等待 GC 回收
- 整个过程是线程不安全的;多线程同时 add 可能导致数据丢失或异常
为什么不用链表?为什么不定长?
选择数组是为了支持 O(1) 随机访问(get(index)),这是 ArrayList 的核心优势;而链表虽易增删,但访问要 O(n)。定长数组又无法满足“动态”需求,所以折中采用“预分配 + 懒扩容”策略——既兼顾访问效率,又避免频繁内存分配。
扩容本身有代价(复制开销 + 临时内存占用),因此合理预估初始容量(如 new ArrayList(expectedSize))能减少扩容次数,提升性能。










