在Java数组拷贝技术中,System.arraycopy无疑是性能王者。它直接绕过Java虚拟机层面,深入底层C函数(例如经典的memmove)执行操作,支持重叠区域的安全拷贝,语义可靠且零内存分配、无GC开销。这意味着在缓冲区数据平移、负载提取、缓存刷新等高频率场景中,它能最大程度地压榨系统性能。简单来说,关键不在于“能不能拷贝”,而在于“如何搬移更稳定、更快速、更可控”。

在实际的数据缓冲与交换场景中,数组拷贝早已不再是简单的复制粘贴,它直接影响系统的低延迟与高吞吐能力。选择合适的技术方案,就等于赢在了性能起跑线上。
缓冲区数据平移:用 System.arraycopy 实现零分配搬运
在NIO通信、环形缓冲区(Ring Buffer)、消息队列底层等场景,最怕频繁创建新数组导致的GC抖动。一个稳妥的做法是复用现有缓冲区,依靠System.arraycopy实现内存级平移,一劳永逸:
- 左移一位(例如消费队首元素):直接使用
System.arraycopy(arr, 1, arr, 0, arr.length - 1)。JVM会自动处理重叠区域的安全策略,无需额外空间,操作干净利落。 - 提取负载段(例如从完整数据包中切出有效载荷):
System.arraycopy(packet, headerLen, payloadBuf, 0, payloadLen)。目标指向预先分配的固定缓冲区,省心省力。 - 批量刷新缓存:
System.arraycopy(newData, 0, cacheArray, 0, newData.length)。这种方式能避免引用替换带来的可见性问题,读线程看到的是完全一致的内存快照。
安全交换与防御性拷贝:避开浅拷贝陷阱
数组本身是一个对象。使用clone()或Arrays.copyOf()对基本类型数组是安全的——它们会将值原样复制到新数组。但对象数组(例如String[]、MyBean[])则不同:它们只执行浅拷贝,新数组里存储的依然是原对象的引用。一旦任何一方修改了对象内部状态,原始数组也会随之改变。
- 当真正需要隔离时,只能手动进行深拷贝:要么对每个元素调用其
clone()(前提是类实现了Cloneable),要么通过序列化或JSON反序列化来绕开引用共享。 - 交换两个数组的引用?直接赋值即可:
temp = a; a = b; b = temp。注意,这只是指针交换,并不搬动内容。 - 交换数组内的两个元素?建议使用异或(仅适用于int/long等整型)或临时变量。需注意包装类的缓存问题——比如
Integer在-128~127区间会共享实例,直接赋值容易踩坑。
动态扩容与分段分发:结合 Arrays.copyOf 与业务逻辑
在日常开发中,Arrays.copyOf和copyOfRange因为封装了目标数组的创建逻辑,成为最顺手的选择。它们天然适合缓冲增长和任务切片场景:
- 扩容并保留旧数据:
data = Arrays.copyOf(data, data.length * 2)。新腾出的位置自动填充默认值(0或null),省去手动初始化。 - 按CPU核心数切分处理器列表:
Handler[] subHandlers = Arrays.copyOfRange(allHandlers, startIdx, endIdx)。快速生成当前线程的本地副本,彻底消除跨段竞争。 - 构建不可变快照:注册变更时,使用
copyOf批量复制当前handler数组,再通过原子更新volatile引用,让读端实现无锁访问,性能直接拉满。
小数据量慎用 native 拷贝:性能拐点需实测
System.arraycopy虽然快速,但JNI调用本身有固定开销。当拷贝长度在16个元素以下时,简单的for循环反而可能更快——尤其是在高频调用的热路径上。
- 建议对关键路径执行JMH基准测试,尤其要区分小批量(例如1024)与大批量场景。
- 避免在循环体内反复调用
arraycopy去处理单个元素;最好一次性合并为批量操作,效率会显著提升。 - 并发写同一个目标数组区域时,务必加锁或改用不可变加引用替换的模式,否则数据撕裂会在瞬间发生。
说到底,选择合适的技术方案,永远比盲目追求某个“最快”的方法更重要。
