System.arraycopy详解

一、前言

对数组的复制,有四种方法:

  • for
  • clone
  • System.arraycopy
  • arrays.copyof

下面分析一下最常用的System.arraycopy()

二、源码拜读

1、源码

2、参数分析

  • Object src : 原数组
  • int srcPos : 从元数据的起始位置开始
  • Object dest : 目标数组
  • int destPos : 目标数组的开始起始位置
  • int length : 要copy的数组的长度

三、深拷贝与浅拷贝

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

1、简单类型(深拷贝)

package com.guor.test.javaSE.collection;

import java.util.Arrays;

public class ArrayTest {

	public static void main(String[] args) {
		getStringArrayFromObjectArray2();
	}
	
	private static void copySelf() {
		int[] ids = { 1, 2, 3, 4, 5 };  
		System.out.println(Arrays.toString(ids));
		//System.arraycopy(src, srcPos, dest, destPos, length);
		// 把从索引0开始的2个数字复制到索引为3的位置上  
		System.arraycopy(ids, 0, ids, 3, 2);
		System.out.println(Arrays.toString(ids));//[1, 2, 3, 1, 2]
	}
	
	private static void copyToOther() {
		int[] ids = { 1, 2, 3, 4, 5 };  
		//将数据的索引1开始的3个数据复制到目标的索引为0的位置上  
		int[] other = new int[5];
		System.arraycopy(ids, 1, other, 0, 3);
		System.out.println(Arrays.toString(ids));//[1, 2, 3, 4, 5]深复制
		System.out.println(Arrays.toString(other));//[2, 3, 4, 0, 0]
	}
	
	//如果是类型转换问题,获取整形
	private static void getIntegerArrayFromObjectArray() {
		Object[] obj1 = { 1, 2, 3, "4", "5" }; 
		Integer[] obj2 = new Integer[5];
		
		try {
			System.arraycopy(obj1, 0, obj2, 0, obj1.length);
		} catch (Exception e) {
			System.out.println("transfer exception:"+e);
		}
		System.out.println(Arrays.toString(obj1));
		System.out.println(Arrays.toString(obj2));
	}
	
	//获取Object数组中的字符串类型数据
	private static void getStringArrayFromObjectArray1() {
		Object[] obj3 = { 1, 2, 3, "4", "5" }; 
		String[] obj4 = new String[5];
		try {
			System.arraycopy(obj3, 2, obj4, 2, 3);
		} catch (Exception e) {
			//transfer exception:java.lang.ArrayStoreException
			System.out.println("transfer exception:"+e);
		}
		System.out.println(Arrays.toString(obj3));
		//[null, null, null, null, null]
		System.out.println(Arrays.toString(obj4));
	}
	
	//获取Object数组中的字符串类型数据
	private static void getStringArrayFromObjectArray2() {
		Object[] obj3 = { 1, 2, 3, "4", "5" }; 
		String[] obj4 = new String[5];
		try {
			System.arraycopy(obj3, 3, obj4, 3, 2);
		} catch (Exception e) {
			System.out.println("transfer exception:"+e);
		}
		System.out.println(Arrays.toString(obj3));
		//[null, null, null, 4, 5]
		System.out.println(Arrays.toString(obj4));
		obj3[3] = "zhangssan";
		System.out.println("查看是浅复制还是深复制~~~~~");
		System.out.println(Arrays.toString(obj3));
		System.out.println(Arrays.toString(obj4));
	}
}

只有普通数据类型和String类型是深拷贝! 

2、二维数组(浅拷贝)

//二维数组
public static void twoArray() {
    int[] arr1 = {1, 2};
    int[] arr2 = {3, 4};
    int[] arr3 = {5, 6};

    int[][] src = new int[][]{arr1, arr2, arr3};
    
    print("原始模样:", src);
    int[][] dest = new int[3][];
    System.arraycopy(src, 0, dest, 0, 3);
    
    System.out.println("改变前");
    print("src = ", src);
    print("dest = ", dest);

    //原数组改变后观察新数组是否改变,改变->浅复制,不改变->深复制
    src[0][0] = -1;

    System.out.println("改变后");
    print("src = ", src);
    print("dest = ", dest);
}

//二维数组toString()
private static void print(String string, int[][] arr) {
    System.out.print(string);
    for (int[] a : arr) {
        for (int i : a) {
            System.out.print(i + " ");
        }
        System.out.print(",");
    }
    System.out.println();
}

二维数组属于浅拷贝,原始数组改变后,复制的数据也发生了改变! 

3、对象复制(深拷贝?)

四、System.arraycopy是不安全的

1、代码实例

多线程对数组进行复制,看System.arraycopy线程是否安全?

package com.guor.test.javaSE.collection;

import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ArrayTest2 {
	private static int[] arrayOriginal = new int[1024 * 1024 * 10];
    private static int[] arraySrc = new int[1024 * 1024 * 10];
    private static int[] arrayDest = new int[1024 * 1024 * 10];
    private static ReentrantLock lock = new ReentrantLock();

    private static void modify() {
        for (int i = 0; i < arraySrc.length; i++) {
            arraySrc[i] = i + 1;
        }
    }

    private static void copy() {
        System.arraycopy(arraySrc, 0, arrayDest, 0, arraySrc.length);
    }

    private static void init() {
        for (int i = 0; i < arraySrc.length; i++) {
            arrayOriginal[i] = i;
            arraySrc[i] = i;
            arrayDest[i] = 0;
        }
    }

    private static void doThreadSafeCheck() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("run count: " + (i + 1));
            init();
            Condition condition = lock.newCondition();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.lock();
                    condition.signalAll();
                    lock.unlock();
                    copy();
                }
            }).start();


            lock.lock();
            // 这里使用 Condition 来保证拷贝线程先已经运行了.
            condition.await();
            lock.unlock();

            Thread.sleep(2); // 休眠2毫秒, 确保拷贝操作已经执行了, 才执行修改操作.
            modify();

            // 如果 System.arraycopy 是线程安全的, 那么先执行拷贝操作, 再执行修改操作时, 不会影响复制结果, 因此 arrayOriginal 必然等于 arrayDist; 
            if (!Arrays.equals(arrayOriginal, arrayDest)) {
                throw new RuntimeException("System.arraycopy is not thread safe");
            }
        }
    }

    public static void main(String[] args) throws Exception {
        //doThreadSafeCheck();
        executeTime();
    }
    
    private static void executeTime() {
    	String[] srcArray = new String[1000000];
        String[] forArray = new String[srcArray.length];
        String[] arrayCopyArray  = new String[srcArray.length];
        
        //初始化数组
        for(int i  = 0 ; i  < srcArray.length ; i ++){
            srcArray[i] = String.valueOf(i);
        }
        
        long forStartTime = System.currentTimeMillis();
        for(int index  = 0 ; index  < srcArray.length ; index ++){
            forArray[index] = srcArray[index];
        }
        long forEndTime = System.currentTimeMillis();
        System.out.println("for方式复制数组:"  + (forEndTime - forStartTime));
        
        long arrayCopyStartTime = System.currentTimeMillis();
        System.arraycopy(srcArray,0,arrayCopyArray,0,srcArray.length);
        long arrayCopyEndTime = System.currentTimeMillis();
        System.out.println("System.arraycopy复制数组:"  + (arrayCopyEndTime - arrayCopyStartTime));
    }
}

2、代码思路分析

  1. arrayOriginal 和 arraySrc 初始化时是相同的, 而 arrayDist 是全为零的.

  2. 启动一个线程运行 copy() 方法来拷贝 arraySrc 到 arrayDist 中.

  3. 在主线程执行 modify() 操作, 修改 arraySrc 的内容. 为了确保 copy() 操作先于 modify() 操作, 我使用 Condition, 并且延时了两毫秒, 以此来保证执行拷贝操作(即System.arraycopy) 先于修改操作.

  4. 根据第三点, 如果 System.arraycopy 是线程安全的, 那么先执行拷贝操作, 再执行修改操作时, 不会影响复制结果, 因此 arrayOriginal 必然等于 arrayDist; 而如果 System.arraycopy 是线程不安全的, 那么 arrayOriginal 不等于 arrayDist.

 3、控制台输出

往期精彩内容:

Java知识体系总结(2021版)

超详细的springBoot学习笔记

Java多线程基础知识总结(绝对经典)

Java面试题总结(附答案)

Vue基础知识总结(绝对经典)

常见数据结构与算法整理总结

猜你喜欢

转载自blog.csdn.net/guorui_java/article/details/113187970