`

System:System.arraycopy方法详解

    博客分类:
  • java
 
阅读更多

 转自:https://segmentfault.com/a/1190000009922279

 

 

看 JDK 源码的时候,Java 开发设计者在对数组的复制时,通常都会使用 System.arraycopy() 方法。

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

  • for

  • clone

  • System.arraycopy

  • arrays.copyof

本文章主要分析 System.arraycopy() ,带着几个问题去看这个方法:

  • 深复制,还是浅复制

  • String 的一维数组和二维数组复制是否有区别

  • 线程安全,还是不安全

  • 高效还是低效

System.arraycopy() 的 API :

public static void arraycopy(
                             Object src,  //源数组
                             int srcPos,  //源数组的起始位置
                             Object dest, //目标数组
                             int destPos, //目标数组的起始位置
                             int length   //复制长度
                             )

深复制还是浅复制

代码:对象数组的复制:

public class SystemArrayCopyTestCase {

    public static void main(String[] args) {
        User[] users = new User[] { 
                new User(1, "seven", "seven@qq.com"), 
                new User(2, "six", "six@qq.com"),
                new User(3, "ben", "ben@qq.com") };// 初始化对象数组
        
        User[] target = new User[users.length];// 新建一个目标对象数组
        
        System.arraycopy(users, 0, target, 0, users.length);// 实现复制
        
        System.out.println("源对象与目标对象的物理地址是否一样:" + (users[0] == target[0] ? "浅复制" : "深复制"));  //浅复制
        
        target[0].setEmail("admin@sina.com");
        
        System.out.println("修改目标对象的属性值后源对象users:");
        for (User user : users) {
            System.out.println(user);
        }
        //
        //
        //
    }
}

class User {
    private Integer id;
    private String username;
    private String email;

    // 无参构造函数
    public User() {
    }

    // 有参的构造函数
    public User(Integer id, String username, String email) {
        super();
        this.id = id;
        this.username = username;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", email=" + email + "]";
    }
}

图示:对象复制的图示

clipboard.png

所以,得出的结论是,System.arraycopy() 在拷贝数组的时候,采用的使用潜复制,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。


一维数组和多维数组的复制的区别

代码:一维数组的复制

        String[] st  = {"A","B","C","D","E"};
        String[] dt  = new String[5];
        System.arraycopy(st, 0, dt, 0, 5);
        
        //改变dt的值
        dt[3] = "M";
        dt[4] = "V";
        
        System.out.println("两个数组地址是否相同:" + (st == dt)); //false
        
        for(String str : st){
            System.out.print(" " + str +" ");   // A  B  C  D  E 
            
        }
        System.out.println(); 
        for(String str : dt){
            System.out.print(" " + str +" ");   // A  B  C  M  V 
        }

使用该方法对一维数组在进行复制之后,目标数组修改不会影响原数据,这种复制属性值传递,修改副本不会影响原来的值。

但是,请重点看以下代码:

        String[] st  = {"A","B","C","D","E"};
        String[] dt  = new String[5];
        System.arraycopy(st, 0, dt, 0, 5);

       for(String str : st){
            System.out.print(" " + str +" ");   // A  B  C  D  E 
            
        }
        System.out.println(); 
        for(String str : dt){
            System.out.print(" " + str +" ");   // A  B  C  D  E 
        }

        System.out.println("数组内对应位置的String地址是否相同:" + st[0] == dt[0]); // true

既然是属性值传递,为什么 st[0] == dt[0] 会相等呢? 我们再深入验证一下:

        String[] st  = {"A","B","C","D","E"};
        String[] dt  = new String[5];
        System.arraycopy(st, 0, dt, 0, 5);
        dt[0] = "F" ;
        
        for(String str : st){
            System.out.print(" " + str +" ");   // A  B  C  D  E 
            
        }
        System.out.println(); 
        for(String str : dt){
            System.out.print(" " + str +" ");   // F  B  C  D  E 
        }


        System.out.println("数组内对应位置的String地址是否相同:" + st[0] == dt[0]); // false

为什么会出现以上的情况呢?

通过以上两段代码可以推断,在System.arraycopy()进行复制的时候,首先检查了字符串常量池是否存在该字面量,一旦存在,则直接返回对应的内存地址,如不存在,则在内存中开辟空间保存对应的对象。

代码:二维数组的复制

        String[][] s1 = {
                    {"A1","B1","C1","D1","E1"},
                    {"A2","B2","C2","D2","E2"},
                    {"A3","B3","C3","D3","E3"}
                        };
        String[][] s2 = new String[s1.length][s1[0].length];  
        
        System.arraycopy(s1, 0, s2, 0, s2.length);  
        
        for(int i = 0;i < s1.length ;i++){ 
         
           for(int j = 0; j< s1[0].length ;j++){  
              System.out.print(" " + s1[i][j] + " ");
           }  
           System.out.println();  
        }  
        
        //  A1  B1  C1  D1  E1 
        //  A2  B2  C2  D2  E2 
        //  A3  B3  C3  D3  E3 
        
        
        s2[0][0] = "V";
        s2[0][1] = "X";
        s2[0][2] = "Y";
        s2[0][3] = "Z";
        s2[0][4] = "U";
        
        System.out.println("----修改值后----");  
        
        
        for(int i = 0;i < s1.length ;i++){  
               for(int j = 0; j< s1[0].length ;j++){  
                  System.out.print(" " + s1[i][j] + " ");
               }  
               System.out.println();  
         }  

        //  Z   Y   X   Z   U 
        //  A2  B2  C2  D2  E2 
        //  A3  B3  C3  D3  E3 

上述代码是对二维数组进行复制,数组的第一维装的是一个一维数组的引用,第二维里是元素数值。对二维数组进行复制后后,第一维的引用被复制给新数组的第一维,也就是两个数组的第一维都指向相同的“那些数组”。而这时改变其中任何一个数组的元素的值,其实都修改了“那些数组”的元素的值,所以原数组和新数组的元素值都一样了。


线程安全,还是不安全

代码:多线程对数组进行复制 (java中System.arraycopy是线程安全的吗? )

public class ArrayCopyThreadSafe {
    private static int[] arrayOriginal = new int[1024 * 1024 * 10];
    private static int[] arraySrc = new int[1024 * 1024 * 10];
    private static int[] arrayDist = 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, arrayDist, 0, arraySrc.length);
    }

    private static void init() {
        for (int i = 0; i < arraySrc.length; i++) {
            arrayOriginal[i] = i;
            arraySrc[i] = i;
            arrayDist[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();

            if (!Arrays.equals(arrayOriginal, arrayDist)) {
                throw new RuntimeException("System.arraycopy is not thread safe");
            }
        }
    }

    public static void main(String[] args) throws Exception {
        doThreadSafeCheck();
    }
}

这个例子的具体操作是:

  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.


根据上面的推理, 运行一下程序, 有如下输出:

run count: 1
run count: 2
Exception in thread "main" java.lang.RuntimeException: System.arraycopy is not thread safe
    at com.test.ArrayCopyThreadSafe.doThreadSafeCheck(ArrayCopyThreadSafe.java:62)
    at com.test.ArrayCopyThreadSafe.main(ArrayCopyThreadSafe.java:68)

所以,System.arraycopy是不安全的。


高效还是低效

代码:for vs System.arraycopy 复制数组

        String[] srcArray = new String[1000000];
        String[] forArray = new String[srcArray.length];
        String[] arrayCopyArray  = new String[srcArray.length];
        
        //初始化数组
        for(int index  = 0 ; index  < srcArray.length ; index ++){
            srcArray[index] = String.valueOf(index);
        }
        
        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));

通过以上代码,当测试数组的范围比较小的时候,两者相差的时间无几,当测试数组的长度达到百万级别,System.arraycopy的速度优势就开始体现了,根据对底层的理解,System.arraycopy是对内存直接进行复制,减少了for循环过程中的寻址时间,从而提高了效能。

 

 

参考java核心技术中System.arraycopy用法:

public static void main(String[] args) {
        int[] a = {5,4,3,2,1};
        Arrays.sort(a);
        a = (int[])goodCopyOf(a,10);
        System.out.println(Arrays.toString(a));

        String[] s = {"a","b","c"};
        s = (String[])goodCopyOf(s,10);
        System.out.println(Arrays.toString(s));
    }

    public static Object goodCopyOf(Object o,int newLength){
        Class cl = o.getClass();
        if(!cl.isArray()) return null;
        Class ct = cl.getComponentType();
        int length = Array.getLength(o);
        Object newArray = Array.newInstance(ct,newLength);
        System.arraycopy(o,0,newArray,0,Math.min(length,newLength));
        return newArray;
    }

 

 

分享到:
评论

相关推荐

    详解Java合并数组的两种实现方式

    在Java中有多种方式可以实现数组合并,本文将介绍两种常见的实现方式,即使用System.arraycopy()方法和ArrayUtils.addAll()方法。 1. System.arraycopy()方法 System.arraycopy()方法是Java提供的一种本地方法,...

    java基础知识编程编程资料

    在Java中,可以使用`System.arraycopy()`方法来复制数组。 **1. 方法签名** ```java public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) ``` - **参数解释**: - `src...

    java三种字符数组合并的方法.doc

    Java 字符数组合并方法详解 Java 语言中,数组合并是指将多个数组元素合并到一个新的数组中。这种操作在实际开发中非常常见,本文将介绍三种字符数组合并的方法。 方法一:使用循环遍历 在第一个方法中,我们使用...

    Java ArrayList.add 的实现方法

    需要注意的是,在 add(int index, E element) 方法中,我们使用 System.arraycopy 方法来复制数组元素,这是一个 native 方法,它的实现是基于 C++ 语言的。很多人认为 System.arraycopy 方法是调用底层的 memcpy ...

    Java System类详解_动力节点Java学院整理

    - `System.arraycopy()`:高效地复制一个数组的一部分到另一个数组。 5. **JVM操作**: - `System.gc()`:触发垃圾收集,虽然不推荐直接使用,但在某些场景下可能需要手动调用。 - `System.runFinalization()`:...

    java数组的初始化及操作详解

    3. 数组的复制:使用 System.arraycopy() 方法或 Array.copyOf() 方法对当前数组进行复制操作。 4. 数组的排序:使用 Arrays.sort() 方法或冒泡排序法等对数组进行排序。 五、数组的使用 数组在 Java 编程中具有...

    java面向对象编程知识点笔记整理与总结0

    - 数组截取:`System.arraycopy(源数组, 源起始位置, 目标数组, 目标起始位置, 复制长度);` - 数组扩容:`ary = Arrays.copyOf(ary, ary.length + 1);` #### 面向对象的三大原则 - **封装**:隐藏对象的属性和...

    JAVA面试基础

    - 使用`System.arraycopy`方法: ```java System.arraycopy(src, srcPos, dest, destPos, length); ``` - 使用`Arrays.copyOf`方法(JDK 1.6+): ```java Arrays.copyOf(src, length); ``` #### 八、访问...

    收集的工具类JUtils(java源码)

    - `JUtils.arrayCopy(Object[] src, Object[] dest)`: 实现数组的复制,类似于System.arraycopy()。 - `JUtils.equals(Object[] array1, Object[] array2)`: 比较两个数组的内容是否完全相同。 6. **IO操作**: ...

    南开JAVA100题答案

    16. 数组复制:`System.arraycopy()` 方法用于复制数组的一部分到另一个数组。 17. 字符串操作:`str.charAt(n-1)` 获取字符串中的最后一个字符。 18. 双重循环:`for` 循环常用于处理二维数组。`i=0;i;i++` 和 `i...

    高手总结java常用API.doc

    ### Java常用API详解 #### 一、Java面向对象基本概念 Java是一种主要基于面向对象编程(OOP)的语言,除了原始数据类型(如int、double等),几乎所有其他元素都是对象。对象是类的一个实例,拥有其特定的状态和行为...

    Android实现连续点击多次事件的代码详解

    Android实现连续点击多次事件的代码详解 Android实现连续点击多次事件的代码...Android实现连续点击多次事件的代码详解涵盖了数组、时间戳、System.arraycopy()方法等多种知识点,为Android开发者提供了有价值的参考。

    java代码性能优化23种技巧

    **优化说明**: `System.arraycopy()`方法提供了高效的数组复制功能,其内部实现利用了平台特定的优化技术。因此,相比于使用循环逐个复制数组元素,这种方法能够显著提高性能。 #### 五、让访问实例内变量的getter/...

    Container

    - **复制**: `System.arraycopy()`或`Arrays.copyOf()`用于复制数组。 - **比较**: `Arrays.equals()`用于比较两个数组是否相等。 - **排序**: `Arrays.sort()`可以对数组进行排序。 - **查找**: `Arrays.binary...

    机试JavaA.doc

    3. **数组复制**:Java中,可以通过for循环或者System.arraycopy()方法来复制数组。例如,将`array1`复制到`array2`,可以这样实现: ```java int[] array1 = {1, 2, 3}; int[] array2 = new int[array1.length];...

    java程序员笔试题目。

    在Java中,我们可以使用`System.arraycopy()`方法来实现字符串的复制。这里是完成此功能的代码: ```java public char[] strcpy(char[] src) { // 创建一个新的字符数组,长度与源数组相同 char[] dest = new ...

    Java复习资料一页开

    **数组拷贝**:`System.arraycopy()`方法用于将一个数组的一部分复制到另一个数组中,语法如下: ```java System.arraycopy(copyFrom, 2, copyTo, 0, 7); ``` 这里将`copyFrom`数组从索引2开始的7个元素复制到`copy...

    Java中的数组复制(clone与arraycopy)代码详解

    clone方法是Object类的protected方法,用于创建对象的副本,而arraycopy是System类的静态方法,用于将一个数组的内容复制到另一个数组中。 1. clone方法: clone方法是Object类的protected方法,用于创建对象的...

    day01_Object类、常用API每日作业卷1

    8. System类的arraycopy方法: 将字符数组{'i','t','c','a','s','a'}的第二个元素复制到末尾,覆盖原有元素:`char[] chars = {'i','t','c','a','s','a'}; System.arraycopy(chars, 1, chars, 5, 1);` 然后遍历数组...

    实验1 控制语句上机.pdf

    - **数组复制**:学会使用`System.arraycopy()`方法复制数组。 **实验步骤详解**: 1. **生成随机数组**: - 用户输入数组长度N。 - 生成N个100以内的随机正整数并存入数组。 2. **显示数组**: - 输出数组s[]的...

Global site tag (gtag.js) - Google Analytics