在 Java 的世界里,数组那看似好办的 `new int[] {1, 2, 3}` 扩充,实际上是一场关于内存分配的暗度陈仓。别被 `ArrayList` 要么 `Array` 的自动扩容给带偏了,数组扩容操作并没有我们想象中那么“丝滑”。想象一下,你有个装满水的水桶,每次加水量不一样,水是往哪个位置倒?要是桶壁是硬山那样的,直接换个大一点的桶,那多好。但在 Java 里,数组扩容往往伴随着“脏活累活”和“数据迁移”,这个过程比写代码要复杂得多。 最核心的一点是:数组扩容讲究的是同步,而不是异步。当你拍板扩容的时候,你不能只把空间让出来,你得把旧数据给拽走。出于 Java 保证的是线程保险,故此凡是涉及数组扩容的操作,都务必在一个绝对保险的线程里搞定。

这个“绝对保险”意味着啥?意味着在那一瞬间,你不能去处理任何可能影响数据顺序要么触发其他同步块的动作。一旦你尝试在扩容过程中去读写数据,结局往往是灾难性的,就连会害得程序抛出异常要么形成不可预测的行为。

故此,扩容逻辑一般被封装成了一个单独的静态方式,直接对外暴露,让应用层乖乖地只负责调用,不去管里面那串复杂的内存操作。 好家伙,这背后的原理实际上就两个字:重新分配。当你拍板扩容时,JVM 会去寻找一个更大的、连续的内存区域。

比如你有个长度为 10 的数组,后来突然扩容到了 100,这时候 JVM 并不会直接在那段空间里塞进新数据。它得先把旧数据的最终一位(也就是 9)给挤出去,让出来给 10。

这时候,原来的 9 得找个地方去,一般会被“推”到数组末尾,要么说溢出到下一个数组对象里去。

这就好比你在排队的餐厅,原本排第 9 号桌,突然要加一桌到第 10 号,那么原来的 9 就要往后挪一位。 但这还没完。出于要把原来的数据给挤出去,你得先把它们拷贝一份。

要是这数组里有 1000 个元素,你就要把这 1000 个元素的副本都拷贝过来,放到新的大容器里去。

这时候,要是这明明已经是 100 个元素了,为啥还要再拷贝一遍?这就涉及到了一个核心矛盾:扩容不只是是扩容,它还是扩容和拷贝的双重操作。

既然是扩容,那就意味着旧数据还在原来的地方,这时候要是直接复制,那复制起来忒好办了。但一旦旧数据被真正切断了,那就得重新复制一遍。

故此,扩容的本质就是“把旧数据复制一份,塞进新的大桶里”。 举个例子,我们构造一个好办的数组 `int[] arr = {1, 2, 3, 4, 5}`。假设后来扩容到了 100 个元素。

这时候,JVM 会先找到一大片的内存块。

第一,它会把 `arr` 里面所有的数据 (`1, 2, 3...`) 分别拷贝到内存的同一个块里,变成一个新的数组 `temp`。

这一步必不可少,没有这一步,原来的 `arr` 就彻底“挂”在那儿了。做完拷贝之后,`temp` 里的数据已经和原来的 `arr` 没关系了,它们目前是两个独立的实体。 接下来才是真正“扩容”的局部。目前的 `temp` 里只有 5 个元素,但我们需求 100 个位置。

这时候,JVM 会把 `arr` 中剩余的、没被拷贝的那局部数据(也就是从第 6 个元素启动到末尾的那些 `4` 和 `5`),直接放入 `temp` 的后续位置去。

这一步叫“溢出”,实际上就是利用了内存的连续性。目前 `temp` 里终于有 100 个元素了,而原来的 `arr` 只剩下空座位了。 最终一步,是把 `arr` 里的空座位换成 `temp` 里的数据。

这时候,`arr` 变成了一个新的数组,它的值就是 `temp` 的内容。供应用程序使用的数组已经焕然一新,拥有 100 个元素了。 你可能会问,为啥还要如此费事?

为啥不能直接让 `temp` 和 `arr` 共用内存?这就回到了 Java 的设计哲学。Java 是垃圾回收器(GC)的语言,它准对象被引用计数为 0 就自动销毁。

要是 `arr` 和 `temp` 共用内存,那它们就是同一块地皮。一旦 `arr` 被 GC 回收,`temp` 里的那些数据块也就跟着消亡了,那原本拷贝出来的数据不就彻底丢了吗?并且,要是两个数组共享内存,你赶明儿再扩容,可能会出于数据冲突而再次触发拷贝。

故此,拆分内存是为了保证数据的保险和可控,哪怕这个过程慢一点点,也总比出错要划算。 最终,当扩容操作搞定之后,原来的数组 `arr` 会变成一个只包含旧数据副本的“死”数组,再也无法被使用了。而那个新的大容器 `temp` 就成为了真正的工作区,里面趴着 100 个元素。

这一刻,你当作只是好办的 `resize`,实际上背后是一个数据搬运、一个内存分配、就连可能是一段数据的溢出过程。 Java 数组扩容,就是在牺牲一点工夫,来换取内存管理上的绝对保险和数据的整个性。

这就是为啥你在编写高性能代码时,要格外注意在扩容关键路径上避免多线程访问,还有如何优雅地管理数组的生命周期。