实现数组复制常用的5种方式及组合类的深复制(深拷贝)

2022-06-21 00:00:00 数组 组合 复制

在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) 若srcdest指定的是同一个数组,则复制的过程是:先将src数组中[srcPos, srcPos + length - 1]的字符序列复制到一个临时数组temp,然后再将该临时数组temp复制到dest数组的指定位置destPos。
b) 以下几种情况会抛出ArrayStoreException异常:

src指向的不是数组类型;
dest指定的不是数组类型;
src和dest存储的数据类型不一致;

c) 图解复制过程。
《实现数组复制常用的5种方式及组合类的深复制(深拷贝)》
三、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对象的引用对象,否则该copyobj中的引用对象是指向同一个内存地址的。若是基本数据类型或不可变对象,则不需要此过程。即对象本身的字段不会被复制,而是以赋值操作进行所谓的“伪复制”。
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 &gt; 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"}

《实现数组复制常用的5种方式及组合类的深复制(深拷贝)》
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;
	}
    原文作者:wadreamer
    原文地址: https://blog.csdn.net/weixin_41427129/article/details/105778675
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章