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

在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) 图解复制过程。
在这里插入图片描述
三、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"}

在这里插入图片描述
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;
	}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值