目录
一、深拷贝与浅拷贝解析
深拷贝与浅拷贝最大的区别就是拷贝过程中对象的属性是否指向的是同一个对象引用。
浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。
1.对于八种基本数据类型说,它们的拷贝是值传递,修改拷贝对象的数值,不会影响到被拷贝对象的数值。
2.对于String 类型来说,虽然它是引用类型,但是String类型的数据是存放在常量池中的,是无法修改的。所以当拷贝的对象进行修改的时候,会把引用指向了新的地址,而不是在原来的地址上进行修改,所以也不会影响被拷贝的数据。
3.对于其他引用数据类型来说,如果是浅拷贝,多个对象会指向同一个引用,对其中一个对象进行修改,也会改变另一个对象的值。
深拷贝就很好理解,是将对象及值复制过来,两个对象修改其中任意一个的值另一个值不会改变。
对于数组这种引用数据类型来说:
浅拷贝
看一段代码:
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,4,5,6,7,8,9};
int[] array1 = array;
System.out.println("修改前的拷贝数组:"+ Arrays.toString(array1));
array1[0] = 10;
System.out.println("修改后的拷贝数组:" + Arrays.toString(array1));
System.out.println("原数组:"+Arrays.toString(array));
}
}
打印结果:
修改前的拷贝数组array1:[1, 2, 3, 4, 5, 6, 7, 8, 9]
修改后的拷贝数组array1:[10, 2, 3, 4, 5, 6, 7, 8, 9]
原数组array:[10, 2, 3, 4, 5, 6, 7, 8, 9]
可以发现对array1的修改会影响原来数组array的元素值,这就是浅拷贝。
浅拷贝在堆中不会分配新的内存空间,仅仅是是增加一个引用变量和之前的引用指向相同的堆空间。
深拷贝
同样看一段代码:
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,4,5,6,7,8,9};
int[] array1 = new int[array.length];
for (int i = 0; i < array.length; i++) {
array1[i] = array[i];
}
System.out.println("修改前的拷贝数组:"+ Arrays.toString(array1));
array1[0] = 10;
System.out.println("修改后的拷贝数组:" + Arrays.toString(array1));
System.out.println("原数组:"+Arrays.toString(array));
}
}
打印结果:
修改前的拷贝数组array1:[1, 2, 3, 4, 5, 6, 7, 8, 9]
修改后的拷贝数组array1:[10, 2, 3, 4, 5, 6, 7, 8, 9]
原数组array:[1, 2, 3, 4, 5, 6, 7, 8, 9]
在这段代码中,可以看到对array1的修改不会影响原数组array中的元素值,这就是深拷贝。
对于深拷贝来说,不仅要复制对象的所有基本数据类型,还要为所有引用类型的成员变量申请存储空间,并复制每个引用类型所引用的对象,直到该对象可达的所有对象。
二、数组拷贝的方式
在Java中数组拷贝主要方式大概有四种:
1.for循环来拷贝
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,4,5,6,7,8,9};
int[] array1 = new int[array.length];
for (int i = 0; i < array.length; i++) {
array1[i] = array[i];
}
System.out.println("修改前的拷贝数组:"+ Arrays.toString(array1));
array1[0] = 10;
System.out.println("修改后的拷贝数组:" + Arrays.toString(array1));
System.out.println("原数组:"+Arrays.toString(array));
}
}
打印结果:
修改前的拷贝数组array1:[1, 2, 3, 4, 5, 6, 7, 8, 9]
修改后的拷贝数组array1:[10, 2, 3, 4, 5, 6, 7, 8, 9]
原数组array:[1, 2, 3, 4, 5, 6, 7, 8, 9]
如果数组中的元素类型为基本数据类型,那么for循环拷贝是一种标准的深拷贝。
2.System.arraycopy( )拷贝
arraycopy() 方法位于 java.lang.System 类中
语法:
System.arraycopy(dataType[] srcArray,int srcIndex,int destArray,int destIndex,int length)
其中,srcArray 表示原数组;srcIndex 表示原数组中的起始索引;destArray 表示目标数组;destIndex 表示目标数组中的起始索引;length 表示要复制的数组长度。
length+srcIndex 必须小于等于 srcArray.length,同时 length+destIndex 必须小于等于 destArray.length。
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,4,5,6,7,8,9};
int[] array1 = new int[9];
System.arraycopy(array,0,array1,0,9);
System.out.println("修改前的拷贝数组:"+ Arrays.toString(array1));
array1[0] = 10;
System.out.println("修改后的拷贝数组:" + Arrays.toString(array1));
System.out.println("原数组:"+Arrays.toString(array));
}
}
打印结果:
修改前的拷贝数组:[1, 2, 3, 4, 5, 6, 7, 8, 9]
修改后的拷贝数组:[10, 2, 3, 4, 5, 6, 7, 8, 9]
原数组:[1, 2, 3, 4, 5, 6, 7, 8, 9]
同样,如果数组中元素类型为基本数据类型,那么,System.arraycopy()拷贝也是深拷贝。
3.Arrays.copyOf( )拷贝
Arrays.copyOf( )位于Arrays类中。
语法:
Arrays.copyOf(dataType[ ] srcArray,int length);
其中,srcArray 表示要进行复制的数组,length 表示复制后的新数组的长度。
该方法会将会把 original 数组复制成一个新数组,其中 length 是新数组的长度。
如果 length 小于 original 数组的长度,则新数组就是原数组的前面 length 个元素,如果 length 大于 original 数组的长度,则新数组的前面元索就是原数组的所有元素,后面补充 0(数值类型)、false(布尔类型)或者 null(引用类型)。
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,4,5,6,7,8,9};
int[] array1 = Arrays.copyOf(array,15);
System.out.println("修改前的拷贝数组:"+ Arrays.toString(array1));
array1[0] = 10;
System.out.println("修改后的拷贝数组:" + Arrays.toString(array1));
System.out.println("原数组:"+Arrays.toString(array));
}
}
打印结果:
修改前的拷贝数组:[1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0]
修改后的拷贝数组:[10, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0]
原数组:[1, 2, 3, 4, 5, 6, 7, 8, 9]
同样,如果数组中元素类型为基本数据类型,那么,Arrays.copyOf( )拷贝也是深拷贝。
4.clone( )拷贝
该方法位于Object类中
语法:
array_name.clone()
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[] {1,2,3,4,5,6,7,8,9};
int[] array1 = array.clone();
System.out.println("修改前的拷贝数组:"+ Arrays.toString(array1));
array1[0] = 10;
System.out.println("修改后的拷贝数组:" + Arrays.toString(array1));
System.out.println("原数组:"+Arrays.toString(array));
}
}
打印结果:
修改前的拷贝数组:[1, 2, 3, 4, 5, 6, 7, 8, 9]
修改后的拷贝数组:[10, 2, 3, 4, 5, 6, 7, 8, 9]
原数组:[1, 2, 3, 4, 5, 6, 7, 8, 9]
与上述几种方式一样,如果数组中元素类型为基本数据类型,那么,clone( )拷贝也是深拷贝。
5.解释
上述只是对几种数组拷贝方法的用法演示,且都是拿基本数据类型举例。可以看到在面对几种基本数据类型的时候,这几种拷贝方式都是深拷贝。
但是在面对数组中元素类型为引用类型的时候,上面几种都是浅拷贝,改变拷贝数组的值,则会影响被拷贝数组中元素的值。所以严格来说这几种方法都是浅拷贝。
三、四种拷贝方式效率比较
可以看下几种拷贝源码推测一下:
1. System.arraycopy( )
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
可以看到System.arraycopy( )方法是用native修饰的本地方法,底层用c或c++实现。所以这个方法应该比较快。
2.Arrays.copyOf( )
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
在Arrays.copyOf( )的源码中,可以看到它本质上还是调用的是System.arraycopy( )方法,所以推测其效率肯定比不上System.arraycopy( )方法。
3.clone( )
protected native Object clone() throws CloneNotSupportedException;
可以看到clone( )也是被native修饰的本地方法,推测也比较快。
4.实测
测试分三组:分别为拷贝100大小的小数组,3000大小的中等数组,和30000大小的大数组。
测试代码:
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = new int[100];
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
System.out.println("原始数组的大小:" + array.length);
useFor(array);
useSystemArraycopy(array);
useArraysCopyOf(array);
useclone(array);
}
public static void useFor(int[] array) {
long start_time = System.nanoTime();
int[] array1 = new int[array.length];
for (int i = 0; i < array.length; i++) {
array1[i] = array[i];
}
long end_time = System.nanoTime();
System.out.println("使用for循环耗时:" + (end_time - start_time)+"ns");
}
public static void useSystemArraycopy(int[] array) {
long start_time = System.nanoTime();
int[] array1 = new int[array.length];
System.arraycopy(array,0,array1,0,array1.length);
long end_time = System.nanoTime();
System.out.println("使用System.arraycopy()方法耗时:" + (end_time - start_time)+"ns");
}
public static void useArraysCopyOf(int[] array) {
long start_time = System.nanoTime();
int[] array1 = new int[array.length];
array1 = Arrays.copyOf(array,array.length);
long end_time = System.nanoTime();
System.out.println("使用Arrays.copyOf()方法耗时:" + (end_time - start_time)+"ns");
}
public static void useclone(int[] array) {
long start_time = System.nanoTime();
int[] array1 = new int[array.length];
array1 = array.clone();
long end_time = System.nanoTime();
System.out.println("使用clone()方法耗时:" + (end_time - start_time)+"ns");
}
}
①小数组
原始数组的大小:100
使用for循环耗时:5100ns
使用System.arraycopy()方法耗时:4100ns
使用Arrays.copyOf()方法耗时:36000ns
使用clone()方法耗时:5900ns
从结果可以看到,数组大小在100时,Arrays.copyOf()耗时几乎是其他几个的七倍,效率最差。其他三种效率差别不大。
②中等大小数组
原始数组的大小:3000
使用for循环耗时:71300ns
使用System.arraycopy()方法耗时:10800ns
使用Arrays.copyOf()方法耗时:45500ns
使用clone()方法耗时:18900ns
从结果可以看到,在数组大小变成3000的时候,System.arraycopy()方法和clone()差距依旧不是很大,但是for循环耗时却突然增多,变成拷贝效率最差的方法了。
③大型数组
原始数组的大小:30000
使用for循环耗时:412800ns
使用System.arraycopy()方法耗时:53300ns
使用Arrays.copyOf()方法耗时:126900ns
使用clone()方法耗时:125800ns
从结果可以看到,在面对大型数组的时候,System.arraycopy()把所有方法都甩了一大圈,效率当之无愧最高,而且数组更大时结果也是如此。与它相对应的for循环拷贝效率随着数组增大更加惨不忍睹,效率垫底。Arrays.copyOf()和clone()方法,随着数组增大,Arrays.copyOf()效率略优一点clone()方法,也仅仅是略优,总体差距不大。
小结:
- 1.在数组较小的时候,大概几百以内,for循环拷贝效率十分优异,但是随着数组稍微再增大一点其效率就惨不忍睹了,所有for循环适合用于小型数组的拷贝。
- 2.在数组为中等长度的时候,比如几千的时候,System.arraycopy()和clone()这两个本地方法的效率差不多,非得比的话,那么还是System.arraycopy()效率高一点点。
- 3.当数组长度较大,大到以万为单位的时候,System.arraycopy()的效率便是当之无愧的第一,所以拷贝大型数组推荐使用这个方法。
- 4.无论何时,Arrays.copyOf()方法都比不上System.arraycopy()方法。