zoukankan      html  css  js  c++  java
  • Java提供的序列化和反序列化

    序列化:是指将Java对象转换为二进制数据。

    反序列化:将二进制数据转换为Java对象。

    与序列化功能相关的类有:

    • java.io.Serializable;
    • java.io.ObjectOutputStream(用于序列化)
    • java.io.ObjectInputStream(用于反序列化)

    序列化对象的前提:

    • 该对象所属的类实现了 java.io.Serializable 接口
    • 该类的成员变量中有一个是序列化id

    反序列化对象的前提:

    • 反序列化对象类也需要实现 java.io.Serializable 接口

      序列化端和反序列化端,序列化对象类和反序列化对象类

    • 两者的类名,包名需要保持一致。否则反序列化时会抛出java.lang.ClassCastException异常。
    • 两者的序列化id需要保持一致。否则反序列化时会抛出java.io.InvalidClassException异常。
    • 两者中的成员变量名保持一致。

      当然,反序列化对象类可以包含额外的成员变量,也可以不包含序列化对象类中的成员变量,只不过这样就无法读取到该成员变量的值。

    序列化对象机制的特点

    • 序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
    • 如果想父类对象也序列化,就需要让父类也实现 Serializable 接口。
    • 实现 Serializable 接口的类,Array,enum 都能能被序列化。

    序列化对象加密传输

      服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
    Java序列化提供的解决方案:
      在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

    示例:

    项目A:序列化对象类:

    package com.java.serializable;
    
    import java.io.ObjectInputStream;
    import java.io.ObjectInputStream.GetField;
    import java.io.ObjectOutputStream;
    import java.io.ObjectOutputStream.PutField;
    import java.io.Serializable;
    
    public class Class03 implements Serializable {
        // 序列化 ID
        private static final long serialVersionUID = 1L;
        // 序列化时不加密
        private String name;
        // 序列化时加密
        private String password="initValue";
        // 测试temp是否也能被自动序列化
        private String temp = "test value of temp";
       // 以下省略setter、getter方法
    
        private void writeObject(ObjectOutputStream oos) {
            try {
                PutField fields = oos.putFields();
                fields.put("password", encrypt(this.password));
                fields.put("name", this.name);
                oos.writeFields();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 将参数加密
        private String encrypt(String pwd) {
            return "encryptValue";
        }
    }
    View Code

    项目A:序列化对象工具类

    package com.java.serializable;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    public class CtestA03 {
    
        public static void main(String[] args) {
            serializeObjectToFile();
        }
        // 存放Java对象二进制数据的文件
        private static final String PATH = "F:\objFile.txt";
        // 将Java对象序列化为二进制数据存储到文件objFile.txt
        private static void serializeObjectToFile() {
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream(PATH));
                Class03 classObj = new Class03();
                classObj.setName("Class03.name");
                oos.writeObject(classObj);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != oos) {
                    try {
                        oos.close();
                    } catch (IOException e) {}
                }
            }
        }
    }
    View Code

    项目B:序列化对象类

    package com.java.serializable;
    
    import java.io.ObjectInputStream;
    import java.io.ObjectInputStream.GetField;
    import java.io.ObjectOutputStream;
    import java.io.ObjectOutputStream.PutField;
    import java.io.Serializable;
    
    public class Class03 implements Serializable {
        // 序列化 ID
        private static final long serialVersionUID = 1L;
        // 昵称:序列化时不加密 
       private String name;
        // 反序列化时需要解密
        private String password="initValue";
        // 测试temp是否能通过反序列化读取到值
        private String temp;
        // 以下省略setter、getter方法
    
        private void readObject(ObjectInputStream ois) {
            try {
                GetField fields = ois.readFields();
                String encryptedVar = (String) fields.get("password", "");
                this.password = decrypt(encryptedVar);
                this.name = (String) fields.get("name", "");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 解密参数
        private String decrypt(String pwd) {
            return "initValue-decrypted";
        }    
    }
    View Code

    项目B:反序列化工具类

    package com.java.serializable;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    
    public class CtestB03 {
        
        public static void main(String[] args) {
            reverseSerializeFileToObject();
        }
       private static final String PATH = "F:\objFile.txt";
        // 反序列化
        private static void reverseSerializeFileToObject() {
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream(PATH));
                Class03 classObj = (Class03) ois.readObject();
                System.out.println("classObj.name="+classObj.getName());// classObj.name=Class03.name
                System.out.println("classObj.password="+classObj.getPassword());// classObj.password=initValue-decrypted
                System.out.println("classObj.temp="+classObj.getTemp());// classObj.temp=null
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != ois) {
                    try {
                        ois.close();
                    } catch (IOException e) {}
                }
            }
        }
    }
    View Code

    执行main方法的结果如下:

    System.out.println("classObj.name="+classObj.getName());// classObj.name=Class03.name

    未加密的成员变量name,反序列化后得到的仍是序列化之前的值。

    System.out.println("classObj.temp="+classObj.getTemp());// classObj.temp=null

    序列化对象的成员变量temp,执行writeObject()时,没有将该变量添加到fields中,所以没有被序列化,反序列化后得到的值为null。

    System.out.println("classObj.password="+classObj.getPassword());// classObj.password=initValue-decrypted

    加密后的成员变量password,会先解密。最后读到的是解密后的密码值initValue-decrypted。

    禁止序列化对象的成员变量

      transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
      在被反序列化后,transient修饰的变量的值为初始值,如 int 型的是 0,对象型的是 null。

    示例:

    项目A:序列化对象类

    package com.java.serializable;
    
    import java.io.Serializable;
    
    public class Class04 implements Serializable {
        // 序列化ID
        private static final long serialVersionUID = 1L;
        // 昵称
        private String nickName;
        // 关键字transient修饰,该变量无法被序列化
        private transient int age = 26;
        // 关键字transient修饰,该变量无法被序列化
        private transient String sex = "man";
        // 以下省略setter、getter方法
    }
    View Code

    项目A:序列化对象工具类

    package com.java.serializable;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    public class CtestA04 {
        public static void main(String[] args) {
            serializeObjectToFile();
        }
        private static final String PATH = "F:\objFile.txt";
        // 序列化对象,转换成二进制数据存储到文件objFile.txt
        private static void serializeObjectToFile() {
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream(PATH));
                Class04 classObj = new Class04();
                classObj.setNickName("nickName");
                oos.writeObject(classObj);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != oos) {
                    try {
                        oos.close();
                    } catch (IOException e) {}
                }
            }
        }
    }
    View Code

    项目B:反序列化对象

    package com.java.serializable;
    
    import java.io.Serializable;
    
    public class Class04 implements Serializable {
        private static final long serialVersionUID = 1L;
        private String nickName;
        private int age;
        private String sex;
        // 以下省略setter、getter方法
    }
    View Code

    项目B:反序列化对象工具类

    package com.java.serializable;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    
    public class CtestB04 {
    
        public static void main(String[] args) {
            reverseSerializeFileToObject();
        }
        // 存储Java对象二进制数据的文件
        private static final String PATH = "F:\objFile.txt";
        /**
         * 将二进制文件反序列化为java的object对象
         * 
         * 反序列化条件:
         * 1.java类的包名一致
         * 2.java类中变量名,变量类型一致
         * 3.序列化ID一致
         */
        private static void reverseSerializeFileToObject() {
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream(PATH));
                Class04 classObj = (Class04) ois.readObject();
                System.out.println("classObj.nickName="+classObj.getNickName());
                System.out.println("classObj.age="+classObj.getAge());
                System.out.println("classObj.sex="+classObj.getSex());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != ois) {
                    try {
                        ois.close();
                    } catch (IOException e) {}
                }
            }
        }
    }
    View Code

    执行结果如下:

    System.out.println("classObj.nickName="+classObj.getNickName());// classObj.nickName=nickName;

    成员变量nickName,可以正常读取值。

    System.out.println("classObj.age="+classObj.getAge());// classObj.age=0
    System.out.println("classObj.sex="+classObj.getSex());// classObj.sex=null

    使用transient 关键字修饰的成员变量age和sex,值为null,说明这两个变量并没有被序列化到二进制文件中。

    序列化对象的存储机制

      Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。

    示例:

    项目A:序列化对象类

    package com.java.serializable;
    
    import java.io.Serializable;
    
    public class Class05 implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        // 以下省略setter、getter方法
    }
    View Code

    项目A:序列化对象工具类

    package com.java.serializable;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    public class CtestA05 {
    
        public static void main(String[] args) {
            serializeObjectToFile();
        }
        private static final String PATH = "D:\objFile.txt";
        // 将 Java 对象序列化到文件objFile.txt中
        private static void serializeObjectToFile() {
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream(PATH));
                // 将对象两次写入文件
                Class05 classObj = new Class05();
                classObj.setName("classObj.name.1");
                oos.writeObject(classObj);
                oos.flush();
                classObj.setName("classObj.name.2");
                oos.writeObject(classObj);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != oos) {
                    try {
                        oos.close();
                    } catch (IOException e) {}
                }
            }
        }
    }
    View Code

    项目B:反序列化对象

    package com.java.serializable;
    
    import java.io.Serializable;
    
    public class Class05 implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        // 以下省略setter、getter方法
    }
    View Code

    项目B:反序列化对象工具类

    package com.java.serializable;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    
    public class CtestB05 {
    
        public static void main(String[] args) {
            reverseSerializeFileToObject();
        }
       private static final String PATH = "D:\objFile.txt";
        // 将二进制文件反序列化为java的object对象
        private static void reverseSerializeFileToObject() {
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream(PATH));
                // 从文件依次读出两个文件
                Class05 classObj1 = (Class05) ois.readObject();
                Class05 classObj2 = (Class05) ois.readObject();
                
                /**
                 * Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。
                 * 反序列化时,恢复引用关系,使得classObj1 和 classObj2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。
                 */
                System.out.println("classObj1 == classObj2 : "+(classObj1 == classObj2));
                System.out.println("classObj1.name="+classObj1.getName());
                System.out.println("classObj2.name="+classObj2.getName());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(null != ois) {
                    try {
                        ois.close();
                    } catch (IOException e) {}
                }
            }
        }
    }
    View Code

    输出结果如下:

    System.out.println("classObj1 == classObj2 : "+(classObj1 == classObj2));// classObj1 == classObj2 : true
    System.out.println("classObj1.name="+classObj1.getName());// classObj1.name=classObj.name.1
    System.out.println("classObj2.name="+classObj2.getName());// classObj1.name=classObj.name.1

      Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。反序列化时,恢复引用关系,使得classObj1 和 classObj2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。

  • 相关阅读:
    SOA设计模式
    MVC架构设计模式
    12周总结
    11周总结
    window环境pycharm中使用cityscapes数据集训练deeplabv3+经验总结
    分析六大质量属性战术
    《一线架构师实践指南》第三章阅读笔记
    pip install 无法安装cv2
    PHP disable_functions Bypass
    MallBuilder逻辑后门(复现)
  • 原文地址:https://www.cnblogs.com/517cn/p/10872802.html
Copyright © 2011-2022 走看看