在Java语言中,一般会采用下面的几种方法进行数组的复制。
a) for循环逐一复制;
b) System.arraycopy()方法
c) Arrays.copyOf()方法
d) 使用clone()方法
e) Arrays.copyOfRange()方法
接下来,我们看下这几种方法的效率和源码,以及如何使用序列化和反序列化实现组合类的深复制。
我们以百万级和千万级的数据作为测试集。测试源码如下:
public static void main(String[] args) throws CloneNotSupportedException {
// int length = 10000000; // 千万级别
int length = 5000000; // 百万级别
Integer[] arr = new Integer[length];
Integer[] arr2 = new Integer[length];
for (int index = 0; index < length; index++) {
arr[index] = new Random().nextInt(length) + 1;
}
// for() 循环方法
long start = System.currentTimeMillis();
for (int index = 0; index < length; index++) {
arr2[index] = arr[index];
}
long end = System.currentTimeMillis();
System.out.println("for()循环方法耗费时间:" + (end - start) + "ms");
// System.arraycopy() 方法
start = System.currentTimeMillis();
System.arraycopy(arr, 0, arr2, 0, length);
end = System.currentTimeMillis();
System.out.println("System.arraycopy()方法耗费时间:" + (end - start) + "ms");
// Arrays.copyOf() 方法
start = System.currentTimeMillis();
arr2 = Arrays.copyOf(arr, length);
end = System.currentTimeMillis();
System.out.println("Arrays.copyOf()方法耗费时间:" + (end - start) + "ms");
// Object.clone() 方法, 数组默认实现了Cloneable接口
start = System.currentTimeMillis();
arr2 = arr.clone();
end = System.currentTimeMillis();
System.out.println("Object.clone()方法耗费时间:" + (end - start) + "ms");
// Arrays.copyOfRange() 方法
start = System.currentTimeMillis();
arr2 = Arrays.copyOfRange(arr, 0, length);
end = System.currentTimeMillis();
System.out.println("Arrays.copyOfRange()方法耗费时间:" + (end - start) + "ms");
}
百万级测试结果:
for()循环方法耗费时间:11ms
System.arraycopy()方法耗费时间:6ms
Arrays.copyOf()方法耗费时间:9ms
Object.clone()方法耗费时间:8ms
Arrays.copyOfRange()方法耗费时间:8ms
千万级测试结果:
for()循环方法耗费时间:20ms
System.arraycopy()方法耗费时间:13ms
Arrays.copyOf()方法耗费时间:16ms
Object.clone()方法耗费时间:15ms
Arrays.copyOfRange()方法耗费时间:16ms
很明显地看出了性能差距,效率从高到低为:
System.arraycopy() > Object.clone() > Arrays.copyOf() > Arrays.copyOfRange() > for()循环
一、for循环逐一复制。
这种方式没啥好说的了,实现灵活,但效率低。
二、System.arraycopy()方法
我们来看下这个方法的源码。
/**
* Copies an array from the specified source array, beginning at the specified
* position, to the specified position of the destination array. A subsequence
* of array components are copied from the source array referenced by
* <code>src</code> to the destination array referenced by <code>dest</code>.
* The number of components copied is equal to the <code>length</code> argument.
* The components at positions <code>srcPos</code> through
* <code>srcPos+length-1</code> in the source array are copied into positions
* <code>destPos</code> through <code>destPos+length-1</code>, respectively, of
* the destination array.
* <p>
* If the <code>src</code> and <code>dest</code> arguments refer to the same
* array object, then the copying is performed as if the components at positions
* <code>srcPos</code> through <code>srcPos+length-1</code> were first copied to
* a temporary array with <code>length</code> components and then the contents
* of the temporary array were copied into positions <code>destPos</code>
* through <code>destPos+length-1</code> of the destination array.
*
* 如果src和dest的指向是同一个数组,则首先复制src指定范围内的数组到一个临时数组temp,再将temp数组复制到目标数组dest的指定位置。
*
* src --> temp --> dest
*
* 如果src和dest参数引用相同的数组对象,则执行复制,就好像先将srcPos到srcPos +
* length-1处的分量复制到具有长度分量的临时数组,然后将临时数组的内容 通过目标数组的destPos + length-1复制到位置destPos。
*
* <p>
* If <code>dest</code> is <code>null</code>, then a
* <code>NullPointerException</code> is thrown.
* <p>
* If <code>src</code> is <code>null</code>, then a
* <code>NullPointerException</code> is thrown and the destination array is not
* modified.
* <p>
* Otherwise, if any of the following is true, an
* <code>ArrayStoreException</code> is thrown and the destination is not
* modified:
* <ul>
* <li>The <code>src</code> argument refers to an object that is not an array.
* <li>The <code>dest</code> argument refers to an object that is not an array.
* <li>The <code>src</code> argument and <code>dest</code> argument refer to
* arrays whose component types are different primitive types
* 即src和dest存储的数据类型不一致
* <li>The <code>src</code> argument refers to an array with a primitive
* component type and the <code>dest</code> argument refers to an array with a
* reference component type.
* <li>The <code>src</code> argument refers to an array with a reference
* component type and the <code>dest</code> argument refers to an array with a
* primitive component type.
* </ul>
* <p>
* Otherwise, if any of the following is true, an
* <code>IndexOutOfBoundsException</code> is thrown and the destination is not
* modified:
* <ul>
* <li>The <code>srcPos</code> argument is negative.
* <li>The <code>destPos</code> argument is negative.
* <li>The <code>length</code> argument is negative.
* <li><code>srcPos+length</code> is greater than <code>src.length</code>, the
* length of the source array.
* <li><code>destPos+length</code> is greater than <code>dest.length</code>, the
* length of the destination array.
* </ul>
* <p>
* Otherwise, if [any actual component](任何一个实际组件) of the source array from
* position <code>srcPos</code> through <code>srcPos+length-1</code> cannot be
* converted to the component type of the destination array by [assignment
* conversion](赋值转换), an <code>ArrayStoreException</code> is thrown. In this
* case, let <b><i>k</i></b> be the smallest nonnegative integer less than
* length such that <code>src[srcPos+</code><i>k</i><code>]</code> cannot be
* converted to the component type of the destination array; when the exception
* is thrown, source array components from positions <code>srcPos</code> through
* <code>srcPos+</code><i>k</i><code>-1</code> will already have been copied to
* destination array positions <code>destPos</code> through
* <code>destPos+</code><i>k</I><code>-1</code> and no other positions of the
* destination array will have been modified. (Because of the restrictions
* already itemized(逐项), this paragraph effectively applies only to the
* situation where both arrays have component types that are reference types.)
*
* 否则,如果无法通过赋值转换将从位置srcPos到srcPos + length - 1
* 的源数组的任何实际组件转换为目标数组的组件类型,则将引发ArrayStoreException。
*
* 在这种情况下,令k为小于长度的最小非负整数,从而无法将src [srcPos + k]转换为目标数组的组件类型。
* 当引发异常时,从位置srcPos到srcPos + k-1的源阵列组件将已经被复制到目标数组位置destPos至destPos +
* k-1,并且目标阵列的其他位置将不会被修改。 (由于已经逐项列出了限制,因此本段仅适用于两个数组都具有引用类型的组件类型的情况。)
*
* @param src
* the source array. 源数组
* @param srcPos
* starting position in the source array. 要复制的源数组的起始位置
* @param dest
* the destination array. 目标数组
* @param destPos
* starting position in the destination data. 目标数组中的起始位置
* @param length
* the number of array elements to be copied. 要复制的字符个数
* @exception IndexOutOfBoundsException
* if copying would cause access of data outside array bounds.
* @exception ArrayStoreException
* if an element in the <code>src</code> array could not be
* stored into the <code>dest</code> array because of a type
* mismatch.
* @exception NullPointerException
* if either <code>src</code> or <code>dest</code> is
* <code>null</code>.
*/
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
通过源码可以发现,该方法是native方法,是调用底层的C/C++实现的,效率会更高,且该方法实现的是浅复制,即是对基本数据类型而言的,拷贝的是一个对象的引用,而不是去创建一个新的对象。此外,从源码可以得出如下结论:
a) 若src
和dest
指定的是同一个数组,则复制的过程是:先将src
数组中[srcPos, srcPos + length - 1]
的字符序列复制到一个临时数组temp
,然后再将该临时数组temp
复制到dest
数组的指定位置destPos。
b) 以下几种情况会抛出ArrayStoreException
异常:
src指向的不是数组类型;
dest指定的不是数组类型;
src和dest存储的数据类型不一致;
c) 图解复制过程。
三、Arrays.copyOf方法
该方法的源码如下所示。
/**
* Copies the specified array, truncating(截断) or padding(填充) with nulls (if
* necessary) so the copy has the specified length. For all indices that are
* valid in both the original array and the copy, the two arrays will contain
* identical values. For any indices that are valid in the copy but not the
* original, the copy will contain <tt>null</tt>. Such indices will exist if and
* only if the specified length is greater than that of the original array. The
* resulting array is of the class <tt>newType</tt>.
*
* 复制指定的数组,截断或填充为空(如果需要),以便副本具有指定的长度。
* 对于在原始数组和副本中均有效的所有索引,两个数组将包含相同的值。
* 对于副本中有效但原始索引无效的任何索引,副本将包含null。
* 当且仅当指定长度大于原始数组的长度时,此类索引才会存在。 所得数组属于 newType类。
*
* @param <U>
* the class of the objects in the original array 源数组中对象的类型
* @param <T>
* the class of the objects in the returned array 返回数组中对象的类
* @param original
* the array to be copied 要被复制的源数组
* @param newLength
* the length of the copy to be returned 要返回的副本长度
* @param newType
* the class of the copy to be returned 要返回的副本的类
* @return a copy of the original array, truncated or padded with nulls to
* obtain the specified length 原始数组的副本,被截断或用null填充以获取指定的长度
* @throws NegativeArraySizeException
* if <tt>newLength</tt> is negative
* @throws NullPointerException
* if <tt>original</tt> is null
* @throws ArrayStoreException
* if an element copied from <tt>original</tt> is not of a runtime
* type that can be stored in an array of class <tt>newType</tt>
* @since 1.6
*/
public static <T, U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
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;
}
可以看到,该方法的本质依旧是调用System.arraycopy()
方法,效率自然低于System.arraycopy()
。同样,该方法实现的是浅复制。
四、使用clone()方法
该方法源码如下所示:
/**
* Creates and returns a copy of this object. The precise meaning of "copy" may
* depend on the class of the object. The general intent is that, for any object
* {@code x}, the expression: <blockquote>
*
* <pre>
* x.clone() != x
* </pre>
*
* </blockquote> will be true, and that the expression: <blockquote>
*
* <pre>
* x.clone().getClass() == x.getClass()
* </pre>
*
* </blockquote> will be {@code true}, but these are not absolute requirements.
* While it is typically the case that(通常情况下): <blockquote>
*
* <pre>
* x.clone().equals(x)
* </pre>
*
* </blockquote> will be {@code true}, this is not an absolute requirement.
* <p>
* By convention(按照惯例), the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent of
* this object (which is being cloned). To achieve this independence, it may be
* necessary to modify one or more fields of the object returned by
* {@code super.clone} before returning it. Typically, this means copying any
* mutable(可变的) objects that comprise(包括) the internal "deep structure" of the
* object being cloned and replacing the references to these objects with
* references to the copies. If a class contains only primitive fields or
* references to immutable(不变的) objects, then it is usually the case that no
* fields in the object returned by {@code super.clone} need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a specific cloning
* operation. First, if the class of this object does not implement the
* interface {@code Cloneable}, then a {@code CloneNotSupportedException} is
* thrown. Note that all arrays are considered(被视为) to implement the interface
* {@code Cloneable} and that the return type of the {@code clone} method of an
* array type {@code T[]} is {@code T[]} where T is any reference or primitive
* type. Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of the
* corresponding fields of this object, as if by assignment(赋值); the contents of
* the fields are not themselves cloned. Thus, this method performs a "shallow
* copy" of this object, not a "deep copy" operation. [clone方法执行的是复制,而不是深复制]
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object whose
* class is {@code Object} will result in throwing an exception at run time.
*
* [Object类没有实现Cloneable接口,因此,在类为Object的对象上调用clone()方法时,运行时会出现异常]
*
* @return a clone of this instance.
* @throws CloneNotSupportedException
* if the object's class does not support the {@code Cloneable}
* interface. Subclasses that override the {@code clone} method can
* also throw this exception to indicate that an instance cannot be
* cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
可以得出如下结论:
a) 因为clone()
方法是native方法,调用底层的C/C++程序,实现的是浅复制,若对象obj
中存在引用类型的对象(即可变对象),则在复制的对象copy
中,需要修改copy
对象的引用对象,否则该copy
和obj
中的引用对象是指向同一个内存地址的。若是基本数据类型或不可变对象,则不需要此过程。即对象本身的字段不会被复制,而是以赋值操作进行所谓的“伪复制”。
b) 所有的数组都被视为已经实现了Cloneable
接口。
c) 如果一个对象要调用clone()
方法,则该对象的类要实现Cloneable
接口。因为Object
类没有实现Cloneable
接口,所以在类为Object
的对象上调用clone()
方法会出现异常。
d) 如果一个类没有实现Cloneable
接口,则其子类即使重写了Cloneable
接口,一样会抛出异常CloneNotSupportException
。
五、Arrays.copyOfRange()方法
源码如下所示:
/**
* Copies the specified range of the specified array into a new array. The
* initial index of the range (<tt>from</tt>) must lie between zero and
* <tt>original.length</tt>, inclusive. The value at <tt>original[from]</tt> is
* placed into the initial element of the copy (unless
* <tt>from == original.length</tt> or <tt>from == to</tt>). Values from
* subsequent elements in the original array are placed into subsequent elements
* in the copy. The final index of the range (<tt>to</tt>), which must be
* greater than or equal to <tt>from</tt>, may be greater than
* <tt>original.length</tt>, in which case <tt>null</tt> is placed in all
* elements of the copy whose index is greater than or equal to
* <tt>original.length - from</tt>. The length of the returned array will be
* <tt>to - from</tt>. The resulting array is of the class <tt>newType</tt>.
*
* 将指定数组的指定范围内的元素复制到新数组。初始索引from的取值必须在[0, original.length)之间。
* original[from]的值是放在副本的初始元素中,除非,from == original.length 或 from == to。
* 来自原始数组中后续元素的值将放入副本中的后续元素中。复制范围的最后一个索引to必须大于或等于from,
* 可能会大于original.length,在这种情况下,将null放置在副本的所有索引
* 大于或等于original.length - from的元素中。 返回的数组的长度为to - from,其所属类型为newType。
*
* @param <U>
* the class of the objects in the original array 源数组的对象所属类
* @param <T>
* the class of the objects in the returned array 返回的数组的对象所属类
* @param original
* the array from which a range is to be copied 要复制指定范围的数组
* @param from
* the initial index of the range to be copied, inclusive
* 指定范围的初始位置,该位置是在复制范围内
* @param to
* the final index of the range to be copied, exclusive. (This index
* may lie outside the array.) 要复制范围的最终索引(不包括)。 (此索引可能位于数组之外。)
* @param newType
* the class of the copy to be returned 要返回的副本的所属类
* @return a new array containing the specified range from the original array,
* truncated or padded with nulls to obtain the required length
* 一个包含原始数组中指定范围的新数组,将其截断或填充为空以获取所需的长度
* @throws ArrayIndexOutOfBoundsException
* if {@code from < 0} or {@code from > original.length}
* @throws IllegalArgumentException
* if <tt>from > to</tt>
* @throws NullPointerException
* if <tt>original</tt> is null
* @throws ArrayStoreException
* if an element copied from <tt>original</tt> is not of a runtime
* type that can be stored in an array of class <tt>newType</tt>.
* @since 1.6
*/
public static <T, U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
@SuppressWarnings("unchecked")
T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}
Arrays.copyOfRange()
实质上也是调用了System.arraycopy()
,且该方法会以默认值填充超出original.length
的元素至返回的副本数组,即引用类型填充null
,int填充0,char填充空字符等。如下述例子:
public static void main(String[] args) throws CloneNotSupportedException {
int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
char[] a1 = new char[] { 'I', 'a', 'm', 's', 'u', 'p', 'e', 'r', 'm', 'a', 'n' };
int[] b = Arrays.copyOfRange(a, 5, 14);
char[] b1 = Arrays.copyOfRange(a1, 5, 20);
for (int i : b) {
System.out.print(i + " ");
}
System.out.println();
for (char c : b1) {
System.out.print(c + " ");
}
System.out.println();
System.out.println("返回的字符数组的长度" + b1.length);
}
结果如下:
6 7 8 9 10 0 0 0 0
p e r m a n
返回的字符数组的长度15
六、实现对象的深复制
1、实现Cloneable接口,并重写clone()
方法,这种方式实现的是浅复制,所以,针对基本数据类型,可以使用该方式实现。而针对引用类型,会出现数据不安全的问题。
public class Person implements Cloneable {
private String name;
private String age;
// 省略getter和setter方法
public Person(String name, String age) {
this.name = name;
this.age = age;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
/* 正确做法
* @Override
* protected Person clone() throws CloneNotSupportedException {
* Person copy = new Person(this.name, this.age);
* return copy;
* }
*/
public static void main(String[] ar1gs) throws CloneNotSupportedException {
Person p1 = new Person("艾斯", "18");
System.out.println(p1.toString());
Person p2 = p1.clone();
p2.setName("艾斯 is changed");
System.out.println(p1.toString());
}
结果如下,p1和p2的引用不同,但指向的是同一个内存地址的数据,造成了数据被修改:
Person{"name" : "艾斯"; "age" : "18"}
Person{"name" : "艾斯 is changed"; "age" : "18"}
2、针对组合类的深复制。
如果一个类里面,又引用其他的类,其他的类又有引用别的类,那么想要深度拷贝必须所有的类及其引用的类都得实现Cloneable接口,重写clone方法,这样以来非常麻烦。记得在讲序列化时,可以使用序列化和反序列化实现深复制,让所有的对象实现序列化接口Serializable
,然后通过序列化、反序列化的方法来深度拷贝对象。
public Person selfClone() {
Person person = null;
try {
// 将对象序列化成为流,因为写在流是对象里的一个拷贝
// 而原始对象扔在存在JVM中,所以利用这个特性可以实现深拷贝
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
// 将流序列化为对象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
person = (Person) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}