You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Java中clone与System.arraycopy比手动循环拷贝数组开销更高?

骑士拨号器数组拷贝性能问题分析

测试背景

我最近在LeetCode上求解骑士拨号器问题,两次提交的代码仅数组拷贝方式不同:

第一次提交(System.arraycopy实现)

提交链接,代码如下:

class Solution {
    public int knightDialer(int n) {
        int[] prev_dp = new int[10];
        int[] curr_dp = new int[10];
        int mod = 1000000007;
        List<List<Integer>> adjList = new ArrayList<>();
        adjList.add(new ArrayList<>(Arrays.asList(4,6)));
        adjList.add(new ArrayList<>(Arrays.asList(6,8)));
        adjList.add(new ArrayList<>(Arrays.asList(7,9)));
        adjList.add(new ArrayList<>(Arrays.asList(4,8)));
        adjList.add(new ArrayList<>(Arrays.asList(0,3,9)));
        adjList.add(new ArrayList<>(Arrays.asList()));
        adjList.add(new ArrayList<>(Arrays.asList(0,1,7)));
        adjList.add(new ArrayList<>(Arrays.asList(2,6)));
        adjList.add(new ArrayList<>(Arrays.asList(1,3)));
        adjList.add(new ArrayList<>(Arrays.asList(2,4)));
        if(n==0) return 0;
        // for 1st step;
        for(int i = 0; i<10;i++){
            curr_dp[i] = 1;
        }
        // prev_dp = curr_dp.clone();
        System.arraycopy(curr_dp,0, prev_dp,0,10);

        
        for(int j = 2; j<=n;j++){
            for(int i = 0; i<10;i++){
                int curr_sum = 0;
                for(int adjNode: adjList.get(i)){
                   curr_sum+=prev_dp[adjNode]; 
                   curr_sum = curr_sum%mod; 
                }
                curr_dp[i] = curr_sum;
            }
            // prev_dp = curr_dp.clone();
            System.arraycopy(curr_dp,0, prev_dp,0,10);
        }
        
        int ans = 0;
        for(int i = 0; i<10;i++){
            ans+=curr_dp[i];
            ans = ans%mod;
        }
        return ans;
    }
}

第二次提交(手动for循环实现)

提交链接,代码如下:

class Solution {
    public int knightDialer(int n) {
        int[] prev_dp = new int[10];
        int[] curr_dp = new int[10];
        int mod = 1000000007;
        List<List<Integer>> adjList = new ArrayList<>();
        adjList.add(new ArrayList<>(Arrays.asList(4,6)));
        adjList.add(new ArrayList<>(Arrays.asList(6,8)));
        adjList.add(new ArrayList<>(Arrays.asList(7,9)));
        adjList.add(new ArrayList<>(Arrays.asList(4,8)));
        adjList.add(new ArrayList<>(Arrays.asList(0,3,9)));
        adjList.add(new ArrayList<>(Arrays.asList()));
        adjList.add(new ArrayList<>(Arrays.asList(0,1,7)));
        adjList.add(new ArrayList<>(Arrays.asList(2,6)));
        adjList.add(new ArrayList<>(Arrays.asList(1,3)));
        adjList.add(new ArrayList<>(Arrays.asList(2,4)));
        if(n==0) return 0;
        // for 1st step;
        for(int i = 0; i<10;i++){
            curr_dp[i] = 1;
        }
        // prev_dp = curr_dp.clone();
        // System.arraycopy(curr_dp,0, prev_dp,0,10);
        for(int i = 0; i<10;i++){
            prev_dp[i] = curr_dp[i];
        }
        
        for(int j = 2; j<=n;j++){
            for(int i = 0; i<10;i++){
                int curr_sum = 0;
                for(int adjNode: adjList.get(i)){
                   curr_sum+=prev_dp[adjNode]; 
                   curr_sum = curr_sum%mod; 
                }
                curr_dp[i] = curr_sum;
            }
            // prev_dp = curr_dp.clone();
            // System.arraycopy(curr_dp,0, prev_dp,0,10);
            for(int l = 0; l<10;l++){
                prev_dp[l] = curr_dp[l];
            }
        }
        
        int ans = 0;
        for(int i = 0; i<10;i++){
            ans+=curr_dp[i];
            ans = ans%mod;
        }
        return ans;
    }
}

测试结果

第一次提交运行速度约为第二次的3倍(第一次耗时70ms,第二次耗时203ms),内存消耗仅为第二次的1/4左右,测试截图如下:
请忽略时间戳,第一次提交耗时70ms,第二次提交耗时203ms

问题解答

首先需要注意:你描述的疑问和实际测试结果刚好相反,实际测试中System.arraycopy的性能远优于手动循环拷贝,原理如下:

1. 运行速度差异原因

  • System.arraycopy是JVM实现的native本地方法,底层直接操作内存块进行批量复制,不需要像手动for循环一样逐次执行Java层的数组边界检查、索引寻址、循环计数等指令,还能直接利用CPU的批量读写指令优化,即使是10个元素的小数组,执行效率也远高于手写循环。
  • 手动for循环的10次赋值操作,在Java层要经历10次数组越界检查、10次内存写入操作,即使JIT即时编译做了优化,也很难达到native层内存批量拷贝的效率,因此耗时更高。
  • 如果是使用clone()方法拷贝数组,会额外创建新的数组对象,带来对象创建和GC的开销,这种场景下才可能比手动循环速度更慢,但你本次测试并没有启用clone的逻辑。

2. 内存消耗差异原因

  • System.arraycopy直接在已经创建好的prev_dp数组内存地址上写入数据,没有额外的对象创建开销,调用native方法的栈内存开销也远低于Java层循环的开销。
  • 手动循环执行过程中会产生循环计数变量、操作数栈临时数据等额外的栈内存开销,加上LeetCode OJ平台的内存统计本身存在一定浮动误差,多次运行统计的平均内存就会比System.arraycopy实现更高。

内容的提问来源于stack exchange,提问作者Aditya Maheshwari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 05:57:01