编辑
2024-02-21
学习记录
00
请注意,本文编写于 407 天前,最后修改于 385 天前,其中某些信息可能已经过时。

目录

前提
简介
深拷贝和浅拷贝
深浅拷贝区别
深拷贝(Deep Copy):
浅拷贝(Shallow Copy):
零拷贝

前提

在项目中有时候会遇到需要多个对象,但是他们的大部分属性的内容都是相同的,那么我们就可以使用克隆的方式来克隆对象,再单独修改不同部分的属性即可,精简多余的set代码。

简介

克隆是指创建一个对象的副本,使得新创建的对象在内容上与原始对象相同。在编程中,克隆是常用的技术之一,它具有以下几个重要用途和优势:

  • 复制对象:使用克隆可以创建一个与原始对象相同的新对象,包括对象的属性和状态。这样可以在不影响原始对象的情况下,对新对象进行修改、操作、传递等。这在某些场景下非常有用,可以避免重新创建和初始化一个对象。

  • 隔离性与保护:通过克隆,可以创建一个独立于原始对象的副本。这样,修改克隆对象时,不会影响到原始对象,从而实现了对象之间的隔离性。这对于多线程环境下的并发操作或者保护重要数据具有重要意义。

  • 性能优化:有时候,通过克隆对象可以提高程序的性能。在某些场景下,对象的创建和初始化过程可能较为耗时,如果需要多次使用这个对象,通过克隆原始对象可以避免重复的创建和初始化过程,从而提高程序的执行效率。

  • 原型模式:克隆在设计模式中有一个重要的角色,即原型模式。原型模式通过克隆来创建对象的实例,而不是使用传统的构造函数。这样可以提供更灵活的对象创建方式,并且避免了频繁的子类化。

在编程中,通常通过实现Cloneable接口和重写clone方法来实现对象的克隆。然而,需要注意的是克隆操作可能存在深拷贝和浅拷贝的区别,在使用时需要根据实际需求选择合适的克隆方式。

深拷贝和浅拷贝

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在克隆(Clone)操作中经常遇到的两个概念,它们描述了克隆操作对于对象内部引用的处理方式。

  1. 浅拷贝(Shallow Copy):

浅拷贝指在克隆操作中,只复制对象本身以及对象内部的基本数据类型的属性,而不复制对象内部的引用类型的属性。 浅拷贝仅仅创建了一个新对象,该对象与原始对象共享对同一引用类型属性的访问。如果原始对象的引用类型属性被修改,浅拷贝的对象也会受到影响。 在浅拷贝中,新对象和原始对象指向同一块内存区域,因此对其中一个对象进行修改可能会影响到另一个对象。

  1. 深拷贝(Deep Copy):

深拷贝指在克隆操作中,除了复制对象本身以及对象内部的基本数据类型的属性外,还要递归地复制对象内部的引用类型的属性。即深度克隆了所有引用类型的属性。 深拷贝创建了一个完全独立的新对象,该对象与原始对象没有任何关联,对新对象和原始对象的修改互不影响。 在深拷贝中,新对象和原始对象分别对应不同的内存区域,它们之间不存在引用关系,因此修改其中一个对象不会影响到另一个对象。

为了实现深拷贝,需要对对象内部的引用类型属性进行递归复制。常见的实现深拷贝的方式包括:

通过序列化和反序列化:将对象序列化为字节流,然后再反序列化为新的对象,这样可以创建一个与原始对象完全独立的副本。 通过逐个复制引用类型属性:对于每个引用类型的属性,创建一个新的实例并将原始对象属性的内容复制到新的实例中。

需要注意的是,并非所有对象都能进行深拷贝。某些对象或者类中的属性可能是不可变的,无需拷贝;某些对象可能包含循环引用,无法完全复制。因此,在进行克隆操作时,需要根据具体情况选择合适的拷贝方式。 深拷贝和浅拷贝的主要区别在于对于对象内部引用类型属性的处理方式。

  1. 数据复制层次的深度:

浅拷贝只复制对象本身以及对象内部的基本数据类型的属性,不会递归地复制引用类型的属性。因此,在浅拷贝中,新对象和原始对象共享对同一引用类型属性的访问。 深拷贝除了复制对象本身和基本数据类型的属性外,还会递归地复制对象内部的引用类型的属性。这样,深拷贝创建了一个完全独立的新对象,与原始对象没有任何关联。

  1. 对象之间的关联性:

浅拷贝得到的新对象与原始对象共享对同一引用类型属性的访问。如果对其中一个对象的引用类型属性进行修改,另一个对象也会受到影响。 深拷贝得到的新对象与原始对象没有任何关联,修改其中一个对象的引用类型属性不会影响到另一个对象。

  1. 内存区域的分配:

在浅拷贝中,新对象和原始对象指向同一块内存区域。因此,对其中一个对象进行修改可能会影响到另一个对象。 在深拷贝中,新对象和原始对象分别对应不同的内存区域,它们之间不存在引用关系,因此修改其中一个对象不会影响到另一个对象。

浅拷贝

实现 Cloneable 接口和重写 clone() 方法:

  • Java 中的 Cloneable 接口是一个标记接口,没有定义任何方法。通过实现 Cloneable 接口并重写 clone() 方法,可以实现对象的浅拷贝。
  • 在 clone() 方法中,调用父类的 clone() 方法,并将其返回值进行类型转换即可完成浅拷贝。
java
class Person implements Cloneable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Main { public static void main(String[] args) { Person person1 = new Person("Alice", 25); try { // 浅拷贝 Person person2 = (Person) person1.clone(); System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25 System.out.println(person2.getName() + " " + person2.getAge()); // Alice 25 person2.setName("Bob"); person2.setAge(30); System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25 System.out.println(person2.getName() + " " + person2.getAge()); // Bob 30 } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }

深拷贝

使用序列化和反序列化:

  • 将对象写入到字节流中,然后再从字节流中读取出来,这个过程会重新创建一个完全独立的对象,实现了深拷贝。
  • 为了实现深拷贝,需要将对象及其关联的对象都实现序列化。
java
import java.io.*; class Address implements Serializable { private String city; private String street; public Address(String city, String street) { this.city = city; this.street = street; } public void setCity(String city) { this.city = city; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public String getStreet() { return street; } } class Person implements Serializable { private String name; private int age; private Address address; public Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setAddress(Address address) { this.address = address; } public String getName() { return name; } public int getAge() { return age; } public Address getAddress() { return address; } } public class Main { public static void main(String[] args) { Address address = new Address("City", "Street"); Person person1 = new Person("Alice", 25, address); // 深拷贝 Person person2 = deepCopy(person1); System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 City System.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Alice 25 City person2.setName("Bob"); person2.setAge(30); person2.getAddress().setCity("New City"); System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 City System.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Bob 30 New City } public static <T extends Serializable> T deepCopy(T object) { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); objectOutputStream.flush(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } } }

深浅拷贝区别

深拷贝(Deep Copy):

  • 适用场景:

当源对象包含引用类型的属性时,如果需要复制对象及其子对象的所有属性,而不仅仅只是复制引用,就需要使用深拷贝。 当希望修改副本对象的属性不影响原始对象时,需要使用深拷贝。

  • 工作原理:

深拷贝将源对象及其关联的全部对象进行递归复制,每个对象都拥有独立的内存空间,修改副本对象不会影响原始对象。

  • 实现方式:

使用递归或者拷贝构造函数来复制对象及其子对象的属性。

  • 示例场景:

复制复杂对象的副本,使其成为独立的个体,例如:拷贝一个包含集合、嵌套对象等的数据结构。 对象图的克隆,当原对象包含子对象,并且对子对象的修改不应该影响原对象时。

浅拷贝(Shallow Copy):

  • 适用场景:

当源对象的属性全为基本数据类型或者不可变对象,并且不需要复制引用类型的属性时,可以使用浅拷贝。 当希望修改副本对象的属性同时影响原始对象时,可以使用浅拷贝。

  • 工作原理:

浅拷贝只复制对象及其引用,而不复制引用指向的实际对象,新旧对象将共享同一个引用对象。修改副本对象会影响原始对象。

  • 实现方式:

通常使用对象的 clone() 方法来进行浅拷贝。

  • 示例场景:

快速创建对象副本,以便在某些操作中对其进行修改,同时保留原始对象。 在某些情况下,共享一部分数据以节省内存和提高性能。

对象都是基本数据类型的话,浅拷贝即可

java
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class User implements Cloneable { private String name; private String age; private Date birth; @Override public User clone() throws CloneNotSupportedException { return super.clone(); } }

对象中包含对象或者集合、数组等内容则需要深拷贝

java
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class SaveElectronicTraceLogRequest implements Serializable { private String name; private String age; private Date birth; private Food food; } @Data @Builder @AllArgsConstructor @NoArgsConstructor public class Food implements Serializable { private String foodName; }

使用深拷贝时注意克隆对象以及他的属性里的引用对象都需要实现序列化接口

java
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setName("张三"); person.setAge(18); person.setBirth(new Date()); person.setFood(new Food("苹果")); System.out.println("person:"+JSON.toJSONString(person)); // Person person2=(Person)person.clone(); Person person2= (Person) deepCopy(person); person2.setBirth(new Date()); person2.getFood().setFoodName("香蕉"); System.out.println("person:"+JSON.toJSONString(person)); System.out.println("person2:"+JSON.toJSONString(person2)); } public static <T extends Serializable > T deepCopy(T object) { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); objectOutputStream.flush(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } } }

零拷贝

零拷贝是一种在数据传输过程中避免不必要的数据复制的技术。零拷贝通常与I/O操作相关,尤其是当数据从一个存储位置移动到另一个存储位置时。通过直接在内存、文件或网络之间传输数据,零拷贝技术可以减少CPU的使用和内存带宽的消耗,从而提高性能。

Java中什么地方用到了零拷贝技术呢?比如:

  • MappedByteBuffer:使用内存映射文件将文件或文件的一部分映射到内存中,从而允许直接访问文件数据而不需要将数据复制到应用程序的内存中。这可以通过FileChannel中的map()方法实现。
  • FileChannel的transferTo/transferFrom方法:这些方法允许数据直接在文件通道或套接字通道之间传输,而不需要先复制到应用程序的内存中。比如,可以使用FileChannel中的transferTo()方法将数据直接从文件发送到网络套接字。
  • DirectBuffer:通过使用直接缓冲区(DirectBuffer),数据可以直接在操作系统的原生内存中进行处理,而不需要先复制到Java堆内存中。这可以通过创建一个ByteBuffer并调用其allocateDirect()方法来实现。

例子

java
public static void main(String[] args) throws IOException { try (FileChannel sourceChannel = new FileInputStream("D://D1.txt").getChannel(); FileChannel destinationChannel = new FileOutputStream("D://D2.txt").getChannel()) { destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); } catch (IOException e) { e.printStackTrace(); } }

本文作者:Weee

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!