前言
在Java编写代码中,对象的拷贝是一个常见的操作。根据拷贝的层次和方式不同,可以分为深拷贝、浅拷贝和零拷贝。本篇文章我们将详细介绍这三种拷贝方式的概念、实现方法以及使用场景,方便大佬学习及面试。
深拷贝
深拷贝是一种创建对象副本的方法,其中新对象与原始对象完全独立。这就意味着新对象的所有字段都被复制,并且如果字段是引用类型,那么递归地执行深拷贝,以确保新对象和原始对象不共享任何内部对象。
如何实现深拷贝呢,接下来介绍一下:
- 手动实现:通过创建一个新的对象,并逐个复制字段的值。如果字段是引用类型,需要递归地创建该字段的新实例,只不过这个过程比较繁琐。
- 使用序列化:将对象序列化为字节流,然后再反序列化回一个新对象。这种方法要求对象及其所有组成部分都是可序列化的。
- 使用克隆:实现Cloneable接口并重写clone()方法。但这种方法有争议,因为它可能不提供真正的深拷贝,除非所有相关的类都正确实现了clone()方法。
举个栗子,我们现在有简单的Person类和一个Address类:
class Address implements Serializable {
private String street;
// ... 其他属性和方法 ...
}
class Person implements Serializable {
private String name;
private Address address;
// ... 其他属性和方法 ...
public Person deepCopy() {
try {
// 使用序列化实现深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Person) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
代码解释:
不难看出,Person类有一个Address类型的字段。我们实现Serializable接口了并使用序列化和反序列化技术,可以创建一个Person对象的深拷贝。这就意味着新创建的Person对象和原始对象是完全独立的,它们不共享任何内部状态。
浅拷贝
浅拷贝创建一个新对象,并复制原始对象的所有非静态字段到新对象。但是,如果字段是引用类型,那么只复制引用而不复制引用的对象。因此,对于引用类型的字段,原始对象和新对象共享同一个内部对象。
那么实现浅拷贝呢,方法如下:
我们通常可以通过创建一个新对象,并且使用构造函数或setter方法将原始对象的值复制到新对象中来实现浅拷贝。Java中的自动装箱和拆箱机制可以简化基本数据类型的复制。
Such as:
使用上面的Person和Address类为例:
class Person {
private String name;
private Address address;
// ... 其他属性和方法 ...
public Person shallowCopy() {
Person copy = new Person();
copy.setName(this.name); // 复制基本数据类型字段
copy.setAddress(this.address); // 只复制引用,不复制引用的对象
return copy;
}
}
对上面的代码解释一下:
我们创建了一个新的Person对象并且复制基本数据类型字段的值和引用类型字段的引用来实现浅拷贝。这意味着修改新对象的name字段不会影响原始对象,但修改新对象的address字段会影响原始对象,因为它们共享同一个Address对象。
零拷贝
零拷贝是一种在数据传输过程中避免不必要的数据复制的技术。零拷贝通常与I/O操作相关,尤其是当数据从一个存储位置移动到另一个存储位置时。通过直接在内存、文件或网络之间传输数据,零拷贝技术可以减少CPU的使用和内存带宽的消耗,从而提高性能。
Java中什么地方用到了零拷贝技术呢?比如:
- MappedByteBuffer:使用内存映射文件将文件或文件的一部分映射到内存中,从而允许直接访问文件数据而不需要将数据复制到应用程序的内存中。这可以通过FileChannel中的map()方法实现。
- FileChannel的transferTo/transferFrom方法:这些方法允许数据直接在文件通道或套接字通道之间传输,而不需要先复制到应用程序的内存中。比如,可以使用FileChannel中的transferTo()方法将数据直接从文件发送到网络套接字。
- DirectBuffer:通过使用直接缓冲区(DirectBuffer),数据可以直接在操作系统的原生内存中进行处理,而不需要先复制到Java堆内存中。这可以通过创建一个ByteBuffer并调用其allocateDirect()方法来实现。
如,上面就是Java的NIO库提供了一些零拷贝技术的实现方法。
try (FileChannel sourceChannel = new FileInputStream("source.txt").getChannel();
FileChannel destinationChannel = new FileOutputStream("destination.txt").getChannel()) {
destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
} catch (IOException e) {
e.printStackTrace();
}
我们在上面的代码中就使用FileChannel的transferFrom()方法实现了文件传输的零拷贝。数据直接从源文件通道传输到目标文件通道,而不需要先复制到应用程序的内存中。这种方法在处理大文件时可以提高性能并减少内存消耗。
文章到这里就先结束了,感兴趣的可以订阅专栏哈,后续会继续分享相关的知识点。