java序列化与深度拷贝

Posted PacosonSWJTU

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java序列化与深度拷贝相关的知识,希望对你有一定的参考价值。

【README】

  • 1, 为啥要序列化或序列化的意义?
  • 2,系统间调用的报文格式,大多数是Json字符串(或字节数组);接收方接收json;
  • 3,但当系统调用如RMI,客户端请求服务器获取一个 javabean对象的状态信息,而不是json格式,这个时候 json格式就不合适了;当然了,在发送时我们可以 把 javabean的状态信息的属性转为json;接收时把json对象转为 javabean;很显然,这样太复杂了,而且不利于维护,一旦 javabean的属性做修改,需要改的代码太多
  • 4,基于此, 序列化出现了,序列化可以把对象信息转换为字节数组,保存到媒介中(如磁盘,内存,或传输到网络),后续需要用到的时候,可以直接把字节数组反序列化为java对象,而不需要做其他操作;也可以理解为是 java对象持久化
    • 这可以解决 系统调用间 javabean对象信息传输的问题;即请求前,把对象序列化为字节数组;接收时,把字节数组反序列化(直接解析)为java bean对象(而不需要其他格式转换);
  • 5,序列化的另一个用途是 用于java对象的深拷贝

【补充1】本文给出了3种实现序列化的方式,包括

  • 1种自动方式(实现 Serializable 接口);
  • 2种手动方式(分别实现 Externalizable, Serializable 接口);

【补充2】自动实现与手动实现

  • 自动实现: 程序员无需自定义字段序列化策略,由 java底层默认实现;
  • 手动实现:程序员需要自定义自定序列化策略,哪些需要序列化,哪些不需要;

【1】java序列化实现

1)java序列化定义: 把那些实现了 Serializable 接口的对象转换成一个字节序列;并能够在以后把这个字节 序列完全恢复到原来的对象;实现对 javabean状态的持久化

2)如何理解持久性? 持久性意味着一个对象的生命周期并不取决于 程序是否在执行;就像数据库的持久化一样;但一个对象可以存在于 程序的调用之间,如远程方法调用 RMI;

3)通过序列化可以把java对象以字节格式保存到磁盘,内存,网络流上面;在以后需要的时候,把序列化后的字节恢复(或反序列化)成原来的 javabean的状态;


【2】 序列化实现

【2.1】实现 Serializable 接口自动实现序列化

 javabean序列化

/**
 * @Description 实现Serializable自动实现序列化与反序列化
 * @author xiao tang
 * @version 1.0.0
 * @createTime 2021年11月20日
 */
public class AutoSerializableClass implements Serializable 
    String name;
    Date date;
    Map<String, String> configs;
    // transient 表示不序列化age
    transient int age;

    public AutoSerializableClass(String name) 
        this(name, Collections.emptyMap(), new Date(), 18);
    
    public AutoSerializableClass(String name, Map<String, String> configs) 
        this(name, configs, new Date(), 18);
    
    public AutoSerializableClass(String name, Map<String, String> configs, Date date, int age) 
        this.name = name;
        this.configs = configs;
        this.date = date;
        this.age = age;
    

    public String getName() 
        return name;
    
    public Map<String, String> getConfigs() 
        return configs;
    

    /**
     * @description 基于序列化的深度拷贝
     * @return AutoSerializableClass
     * @author xiao tang
     * @date 2021/11/20
     */
    public AutoSerializableClass deepClone() throws Exception 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        // 把流放在 tyr资源块中,程序结束java会自动关闭流资源
        try (ObjectOutputStream outputStream = new ObjectOutputStream(buf)) 
            outputStream.writeObject(this);
        
        try (ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()))) 
            return (AutoSerializableClass) inputStream.readObject();
        
     
    @Override
    public String toString() 
        return "AutoSerializableClass" +
                "name='" + name + '\\'' +
                ", date=" + date +
                ", configs=" + configs +
                ", age=" + age +
                '';
    

【2.1.1】序列化到磁盘与反序列

public static String path = "d://temp//"; 
/**
     * @description 测试用例-把 AutoSerializableClass 对象序列化到磁盘
     * @author xiao tang
     * @date 2021/11/20
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException 
        // 把 AutoSerializableClass 对象序列化到磁盘
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(path + "my01.out"));
        outputStream.writeObject(new AutoSerializableClass("zhangsan"));
        outputStream.close();
        // 从磁盘读取序列化对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path + "my01.out"));
        AutoSerializableClass objParsed = (AutoSerializableClass) objectInputStream.readObject();
        System.out.println(objParsed.getName()); // zhangsan
    

【2.1.2】深度拷贝

/** 
     * @description 测试用例-基于 Serializable接口自动实现序列化的深度拷贝
     * @author xiao tang
     * @date 2021/11/20 
     */
    public static void main(String[] args) throws Exception 
        Map<String, String> configs = new HashMap<>();
        configs.put("k1", "v1");
        AutoSerializableClass lisi = new AutoSerializableClass("lisi", configs);

        // 深度拷贝到lisi2 和 lisi3
        AutoSerializableClass lisi2 = lisi.deepClone();
        AutoSerializableClass lisi3 = lisi.deepClone();

        // 为lisi2添加配置
        lisi2.getConfigs().put("k2", "v2");
        System.out.println("\\n lisi2==="); // configs=k1=v1, k2=v2, age=0
        System.out.println(lisi2);
        System.out.println("\\n lisi3==="); // configs=k1=v1, age=0
        System.out.println(lisi3);

        // 为lisi3添加配置
        lisi3.getConfigs().put("k3", "v3");
        System.out.println("\\n lisi2==="); // configs=k1=v1, k2=v2, age=0
        System.out.println(lisi2);
        System.out.println("\\n lisi3==="); // configs=k1=v1, k3=v3, age=0 
        System.out.println(lisi3);
    

 lisi2===
AutoSerializableClassname='lisi', date=Sat Nov 20 21:43:50 CST 2021, configs=k1=v1, k2=v2, age=0

 lisi3===
AutoSerializableClassname='lisi', date=Sat Nov 20 21:43:50 CST 2021, configs=k1=v1, age=0

 lisi2===
AutoSerializableClassname='lisi', date=Sat Nov 20 21:43:50 CST 2021, configs=k1=v1, k2=v2, age=0

 lisi3===
AutoSerializableClassname='lisi', date=Sat Nov 20 21:43:50 CST 2021, configs=k1=v1, k3=v3, age=0

【代码解说】

  • 1,可以看到  lisi2 lisi3 是独立的两个对象,且反序列化后,lisi2,lisi3 定义的 configs 不是同一个 ;
  • 2,age 本身初始化为18,但这里为0,是因为 age 被修改为 transient,表示不序列该字段,故age取int默认值0;
  • 3,接口 Serializable 没有任何方法,仅一个标识接口而已;

【2.2】实现  Externalizable 接口手动实现序列化

1,Externalizable接口继承自  Serializable, 有2个方法需要实现;

2,这两个方法 writeExternal 和 readExternal 方法 会分别在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作(对某些字段进行序列化与反序列化,某些不进行如账号密码,private字段不序列化等) ;如 我们可以对age 字段进行序列化和反序列化,即便age被 transient 修饰

public interface Externalizable extends java.io.Serializable 
      void writeExternal(ObjectOutput out) throws IOException;   
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

3,Externalizable 实现类

/**
 * @Description 实现Externalizable手动实现序列化与反序列化
 * @author xiao tang
 * @version 1.0.0
 * @createTime 2021年11月20日
 */
public class ManualExternalizableClass implements Externalizable 
    String name;
    Date date;
    Map<String, String> configs;
    // transient 表示不序列化age
    transient int age;

    public ManualExternalizableClass()
    
    public ManualExternalizableClass(String name) 
        this(name, new Date(), new HashMap<String, String>(), 18);
    
    public ManualExternalizableClass(String name, Date date, Map<String, String> configs, int age) 
        this.name = name;
        this.date = date;
        this.configs = configs;
        this.age = age;
    
    /** 
     * @description 序列化过程自动调用
     * @param out 序列化书写流
     * @author xiao tang
     * @date 2021/11/20 
     */
    @Override
    public void writeExternal(ObjectOutput out) throws IOException 
        out.writeObject(name);
        out.writeObject(date);
        out.writeInt(age);
    
    /** 
     * @description 反序列化过程自动调用
     * @param in 反序列化读入流
     * @author xiao tang
     * @date 2021/11/20 
     */
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException 
        this.name = String.valueOf(in.readObject());
        this.date = (Date) in.readObject();
        this.age = in.readInt();
    
    /**
     * @description 深度拷贝 ManualExternalizableClass
     * @return MyExternalizableClass对象
     * @author xiao tang
     * @date 2021/11/20
     */
    public ManualExternalizableClass deepClone() throws Exception 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        try (ObjectOutputStream outputStream = new ObjectOutputStream(buf)) 
            outputStream.writeObject(this);
        
        try (ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()))) 
            return (ManualExternalizableClass) inputStream.readObject();
        
    

    public void setAge(int age) 
        this.age = age;
    

    @Override
    public String toString() 
        return "ManualExternalizableClass" +
                "name='" + name + '\\'' +
                ", date=" + date +
                ", configs=" + configs +
                ", age=" + age +
                '';
    

【2.2.1】 测试用例,对 Externalizable 对象进行深拷贝

/**
 * @Description 测试用例-实现Externalizable手动实现序列化与反序列化
 * @author xiao tang
 * @version 1.0.0
 * @createTime 2021年11月20日
 */
public class ManualExternalizableClassTest 
    public static void main(String[] args) throws Exception 
        ManualExternalizableClass obj = new ManualExternalizableClass("zhangsan");
        // 深度拷贝
        ManualExternalizableClass newObj = obj.deepClone();
        newObj.setAge(19); // 对新对象的age赋值,不影响老对象
        System.out.println("obj = " + obj); // age=18
        System.out.println("newObj = " + newObj); // age=19
    

obj = MyExternalizableClassname='zhangsan', date=Sat Nov 20 20:32:15 CST 2021, configs=, age=18
newObj = MyExternalizableClassname='zhangsan', date=Sat Nov 20 20:32:15 CST 2021, configs=null, age=19

【代码解说】

  • 1,上述javabean MyExternalizableClass实现了 Externalizable 接口的方法 writeExternal() ,序列化字段name,date, age;实现了方法 readExternal(),反序列化字段 name, date, age;即便age 被 transient 修饰,但我们还可以序列化;
  • 2,这种方式的序列化,需要指定序列化的字段,本文定义为 手动序列化方式

【2.3】实现Serializable手动实现序列化

1)序列化javabean

/**
 * @Description 实现 Serializable 手动实现序列化与反序列化
 * @author xiao tang
 * @version 1.0.0
 * @createTime 2021年11月20日
 */
public class ManualSerializableClass implements Serializable 
    String name;
    Date date;
    Map<String, String> configs;
    // transient 表示不序列化age
    transient int age;

    public ManualSerializableClass(String name) 
        this(name, new HashMap<>(), new Date(), 18);
    
    public ManualSerializableClass(String name, Map<String, String> configs) 
        this(name, configs, new Date(), 18);
    
    public ManualSerializableClass(String name, Map<String, String> configs, Date date, int age) 
        this.name = name;
        this.configs = configs;
        this.date = date;
        this.age = age;
    

    public String getName() 
        return name;
    
    public Map<String, String> getConfigs() 
        return configs;
    
    
    /** 
     * @description 序列化过程自动调用(注意这里并不是重写 Serializable接口方法,即便没有重写,序列化过程也会自动调用,算是java一个缺陷,from thingking-in-java)
     * @param outputStream 书写流
     * @author xiao tang
     * @date 2021/11/20 
     */
    private void writeObject(ObjectOutputStream outputStream) throws IOException 
        outputStream.defaultWriteObject(); // 先调用默认序列化方式
        outputStream.writeInt(age); // 把age 进行手动序列化
    
    /** 
     * @description 反序列化过程自动调用(注意这里并不是重写 Serializable接口方法,即便没有重写,反序列化过程也会自动调用,算是java一个缺陷,from thingking-in-java)
     * @param inputStream 读入流
     * @author xiao tang
     * @date 2021/11/20 
     */
    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException 
        inputStream.defaultReadObject(); // 先调用默认反序列化方式
        age = inputStream.readInt(); // 把 age 手动反序列化
    
    
    /**
     * @description 基于序列化的深度拷贝
     * @return AutoSerializableClass
     * @author xiao tang
     * @date 2021/11/20
     */
    public ManualSerializableClass deepClone() throws Exception 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        try (ObjectOutputStream outputStream = new ObjectOutputStream(buf)) 
            outputStream.writeObject(this);
        
        try (ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()))) 
            return (ManualSerializableClass) inputStream.readObject();
        
    
    @Override
    public String toString() 
        return "AutoSerializableClass" +
                "name='" + name + '\\'' +
                ", date=" + date +
                ", configs=" + configs +
                ", age=" + age +
                '';
    

2)测试用例

/**
 * @Description 测试用例-实现 Serializable 手动实现序列化与反序列化
 * @author xiao tang
 * @version 1.0.0
 * @createTime 2021年11月20日
 */
public class ManualSerializableClassTest 
    public static void main(String[] args) throws Exception 
        ManualSerializableClass oldObj = new ManualSerializableClass("zhangsan");
        ManualSerializableClass newObj = oldObj.deepClone();
        newObj.getConfigs().put("newK", "newV");
        System.out.println("oldObj=" + oldObj); // configs=, age=18
        System.out.println("newObj=" + newObj); // configs=newK=newV, age=18
    

oldObj=MySerializableClassname='zhangsan', date=Sat Nov 20 21:06:37 CST 2021, configs=, age=18
newObj=MySerializableClassname='zhangsan', date=Sat Nov 20 21:06:37 CST 2021, configs=newK=newV, age=18


【小结】

 

以上是关于java序列化与深度拷贝的主要内容,如果未能解决你的问题,请参考以下文章

Java 深度拷贝测试

JavaWeb - 深度拷贝方式和性能对比

JAVA面试题:对象拷贝

Java 深拷贝与浅拷贝概念与代码实现

克隆_浅拷贝和深拷贝

Java面试之对象拷贝