这几天学习数据结构看ArrayList等源码看到好多地方用到 Arrays.copyOf() 和 System.arraycopy() ,现在学习总结一下。
1、看下 System.arraycopy() 方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
arraycopy 是一个native方法,具体实现在底层,其作用是将一个数组中的数据按指定的位置和大小拷贝到另一个数组中指定的位置。其中src是源数组,dest是目标数组,srcPos是源数组的的开始位置,destPos是目标数组的开始位置,length是复制的长度。下面看个小例子理解一下此方法:
public static void main(String[] args) {
int src[] = new int[]{1, 2, 3, 4, 5};
int[] dest = new int[6];
int srcPos = 1;
int destPos = 2;
int length = 3;
System.arraycopy(src, srcPos, dest, destPos, length);
for (int i = 0; i < dest.length; i++) {
System.out.print(dest[i] + " ");
}
}
输出:
0 0 2 3 4 0
上面代码src中放了5个数字,dest开辟的空间是6,刚开始里面默认是6个0,System.arraycopy将源数组src中的数组,从下标为1(srcPos)的位置开始(即从2开始),大小length为3(即src中的2,3,4)复制到目标数组dest中从下标为2(destPos)的位置开始。
System.arraycopy是对内存直接进行复制,减少了for循环过程中的寻址时间,从而提高了效能。不过通过下面对比System.arraycopy和自己复制100000000个数据的时间对比,通过输出毫秒差距不大,System.arraycopy比自己复制快几毫秒到十几毫秒,数据再大我的电脑都内存溢出了。不过既然Java已经提供了这个方法,使用又方便,以后数组的复制可以使用他。
public static void main(String[] args) {
int src[] = new int[100000000];
Random random = new Random();
for (int i = 0; i < src.length; i++) {
src[i] = random.nextInt(Integer.MAX_VALUE);
}
// 通过System.arraycopy复制
int[] dest = new int[src.length];
long startTime = System.currentTimeMillis();
System.arraycopy(src, 0, dest, 0, src.length);
long endTime = System.currentTimeMillis();
System.out.println((endTime - startTime));
// 通过自己复制
int[] dest2 = new int[src.length];
startTime = System.currentTimeMillis();
for (int i = 0; i < src.length; i++) {
dest2[i] = src[i];
}
endTime = System.currentTimeMillis();
System.out.println((endTime - startTime));
}
// 输出:49 56
System.arraycopy() 是浅拷贝还是深拷贝
浅拷贝:如果属性是基本数据类型,拷贝的就是基本数据类型的值;如果属性是引用数据类型(内存地址),拷贝的就是内存地址即数据的引用。而其所指向对象的引用仍然指向原来的对象。
深拷贝:复制出来的所有变量都含有与原来的对象相同的值,那些引用其他对象的变量将指向复制出来的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。深拷贝相比于浅拷贝速度较慢并且花销较大。
看下下面代码:
public class Bean {
public String name;
public Bean(String name) {
this.name = name;
}
}
public static void main(String[] args) {
int[] intSrc = new int[]{1, 2};
int[] intDest = new int[2];
System.arraycopy(intSrc, 0, intDest, 0, 2);
intDest[0] = 5;
for (int i = 0; i < intSrc.length; i++) {
System.out.print(intSrc[i] + "\t"); // 1 2
}
System.out.println();
Bean[] beanSrc = new Bean[]{new Bean("bean1"), new Bean("bean2")};
Bean[] beanDest = new Bean[2];
System.arraycopy(beanSrc, 0, beanDest, 0, 2);
beanDest[0].name = "bean";
for (int i = 0; i < beanSrc.length; i++) {
System.out.print(beanSrc[i].name + "\t"); // bean bean2
}
System.out.println();
int[][] intsSrc = new int[2][2];
int[][] intsDest = new int[2][2];
intsSrc[0][0] = 1;
intsSrc[0][1] = 2;
intsSrc[1][0] = 3;
intsSrc[1][1] = 4;
System.arraycopy(intsSrc, 0, intsDest, 0, 2);
intsDest[0][0] = 5;
for (int i = 0; i < intsSrc.length; i++) {
for (int j = 0; j < intsSrc[i].length; j++) {
System.out.print(intsSrc[i][j] + "\t"); // 5 2 3 4
}
}
}
通过上面代码输出,基本数据类型是值拷贝,引用类型(bean和数组)拷贝后改变目标数组源数组的值也发生了改变,可见System.arraycopy()的拷贝是浅拷贝。
2、看下 Arrays.copyOf() 的几个方法
public static boolean[] copyOf(boolean[] original, int newLength){
boolean[] copy = new boolean[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static byte[] copyOf(byte[] original, int newLength){
byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static char[] copyOf(char[] original, int newLength){
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static double[] copyOf(double[] original, int newLength){
double[] copy = new double[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static float[] copyOf(float[] original, int newLength){
float[] copy = new float[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
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;
}
public static long[] copyOf(long[] original, int newLength){
long[] copy = new long[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static short[] copyOf(short[] original, int newLength){
short[] copy = new short[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static <T> T[] copyOf(T[] original, int newLength){
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType){
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
看上面前8个方法是8个基本数据类型的数组的复制,根据传进来的newLength,开辟出newLength大小的内存给copy,然后调用了System.arraycopy()将original数组中的数据拷贝到刚开辟的内存copy中。其中Math.min(original.length, newLength)表示当要复制的数组的大小newLength大于original数组的大小,就取original数组的大小。
倒数第二个方法copyOf(T[] original, int newLength)是对除了上面8个方法其他数据类型的补充,其参数是传入一个源数据泛型数组,并返回复制后的泛型数组。最后这个方法最后调用了最后一个方法。
最后一个方法 <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) 有三个参数,第一个参数original是源数据数组,newLength是复制后新数组的大小,newType是复制后新数组的数据类型。下面看下具体实现。
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType){
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
其中开始是一个三目运算符,如果newType如果是Object的数组就直接new一个Object数组,否则的话通过反射创建一个newType数据类型的新数组。创建出新数组后还是调用System.arraycopy完成数组的最终复制。
1、((Object)newType == (Object)Object[].class)的必要性,没有这层判断的话不管传进来什么都会通过反射创建一个数组,性能上有损。定义如下:
由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能低于非反射操作,并且应避免在性能敏感应用程序中频繁调用的代码段中。
2、newType.getComponentType()返回的是数组的类型,比如下面
Integer[] integers = new Integer[1];
System.out.println(integers.getClass().getComponentType());
输出:class java.lang.Integer
3、ArrayList中toArray()方法分析
ArrayList中有两个toArray()方法
transient Object[] elementData;
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
其中toArray()方法是将数组中内部所有元素都转换为Object类型了,toArray(T[] a)方法的参数传入一个泛型数组类型T,是将数组内部的所有元素都转换为T(比如传入的泛型是Integer则会将数组内所有元素都转换为Integer)。
看下面代码将list转换为数组分别调用ArrayList的两个方法:
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(); //1
Integer[] list1 = list.toArray(new Integer[list.size()]); //2
Integer[] list2 = (Integer[]) list.toArray(); //3
}
上面代码第三句会报错
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer
报错的原因是list.toArray()返回的是一个内部都是Object类型的数组,强转为Integer类型的数组肯定转不过去,因为String是Object的子类,而String[]不是Object[]的子类。所以对于实际类型是String的Object引用是可以强转成String的。但是 Object[]不能强转成String[],只能采用个个赋值的方式,把里面的引用挨个强转再拷贝过来,所以可以通过list.toArray(new Integer[list.size()])或者可以通过下面这样使用:
Object[] list2 = list.toArray();
for (int i = 0; i < list2.length; i++) {
Integer integer = (Integer) list2[i];
System.out.print(integer + "\t");
}