System.arraycopy 是 Java 中唯一能绕过 Java 层校验、直接调用 JVM 底层内存搬运指令的原语,支持同数组原地重排、严格边界与类型检查、length=0 合法校验,且移位方向需依 srcPos 与 destPos 大小关系选择执行顺序以避免数据覆盖。

System.arraycopy 是 Java 中唯一能绕过 Java 层循环、边界检查和类型校验,直接调用 JVM 底层内存搬运指令(如 memmove 或 SIMD 优化路径)的原语。它不创建新数组,不做对象构造或 finalize 调用,只做地址级数据平移——这正是实现“极速移位重排”的底层根基。
掌握核心参数逻辑:5个参数决定拷贝行为
调用形式为:System.arraycopy(src, srcPos, dest, destPos, length)。关键不是“复制”,而是“按地址块搬运”:
- src 和 dest 可以是同一个数组:此时即为原地重排(如删除元素后前移、环形缓冲区左移)
-
srcPos + length ≤ src.length 且 destPos + length ≤ dest.length:否则立即抛出
ArrayIndexOutOfBoundsException,无静默截断 -
类型兼容性在运行时强校验:int[] 不能拷到 byte[];Object[] 拷到 String[] 会在遇到非 String 元素时抛
ArrayStoreException - length 为 0 是合法操作:不执行搬运,但会完成全部参数检查,可用于快速校验边界
实战高频场景:原地右移与左移的正确写法
所谓“移位”,本质是内存块覆盖。方向错误会导致未读数据被提前覆盖——JVM 不自动纠错,全靠你传参控制顺序:
-
向右移 k 位(例如 arr[2..6] → arr[5..10]):因 destPos > srcPos,必须从高下标往低下标搬,否则 arr[2] 覆盖 arr[3] 后,原 arr[3] 就丢失了。正确写法:
System.arraycopy(arr, 2, arr, 5, 5)。JVM 内部自动倒序执行,等价于 for (int i = 6; i >= 2; i--) arr[i+3] = arr[i] - 向左移 k 位(例如 arr[4..8] → arr[2..6]):因 destPos System.arraycopy(arr, 4, arr, 2, 5)。JVM 自动正向执行,安全无覆盖
-
边界陷阱示例:想把 arr[7..9] 左移 1 位填入 arr[6..8],若误写成
System.arraycopy(arr, 7, arr, 6, 3),实际是安全的(因 6 6 + 3 = 9 刚好踩线,destPos + length == dest.length是允许的,不会越界
性能关键约束:让 arraycopy 真正跑出极限速度
它的快,是有条件的。脱离这些约束,可能比手写 for 还慢:
- 数据量阈值很重要:实测表明,拷贝长度 ≤ 16 时,JNI 调用开销常超过简单循环;建议对小数据走分支判断,避免滥用
- 目标数组必须预先分配好:arraycopy 不负责 new,只搬运。扩容场景必须先 new 新数组,再 copy —— 这正是 ArrayList.add() 内部扩容的标准流程
-
编译期常量更易触发 JIT 内联优化:length 写死为 1024 比变量
int n更可能生成最优汇编指令(如 AVX-512 批量加载) - 基本类型数组优势最大:byte[]、int[] 等走纯字节路径,无 GC write barrier;对象数组仅复制引用,不触发任何对象生命周期方法
典型工程应用:ArrayList 删除与环形缓冲区收缩
这两个案例揭示了 arraycopy 如何被用于真实高性能系统中:
-
ArrayList.remove(int index):内部执行
System.arraycopy(elementData, index+1, elementData, index, size - index - 1)。这不是“算法设计”,而是物理内存重排——把后续所有元素一次性前移一位,时间复杂度 O(1) 搬运,而非 O(n) 循环赋值 -
环形缓冲区 consume(k):当 head=3、size=10、capacity=16,消费 2 个元素后,剩余 8 个元素逻辑上位于 [5,12]。若该区间未跨边界(5+8≤16),则单次
System.arraycopy(buf, 5, buf, 0, 8)就将数据拉直到头部;否则分两段拷贝,仍远快于逐字节移动 - 注意原子性≠线程安全:单次 arraycopy 调用是原子的(要么全成,要么异常中断),但多个线程并发写同一 dest 数组区域,会出现数据撕裂。需外加 synchronized 或使用不可变目标+引用替换

















