porsche911
7/3/2019 - 6:44 AM

序列化

将java对象序列化为二进制文件,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 进行对象的读写。序列化 ID 问题:情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。解决:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。

public class Test implements Serializable {
    private static final long serialVersionUID =1L;
    private static int staticVar = 5;
    private String passWord = "pass";
    private int i;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
    /*private void writeObject(ObjectOutputStream out){
        try {
            String passWord = "pass";
            ObjectOutputStream.PutField putFields= out.putFields();
            System.out.println("原密码:"+passWord);
            passWord = "encryption";//模拟加密
            putFields.put("passWord",passWord);
            System.out.println("加密后的密码:"+passWord);
            out.writeFields();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }*/
    /*private void readObject(ObjectInputStream in){
        try {
            ObjectInputStream.GetField readFiles = in.readFields();
            Object object =readFiles.get("passWord","");
            System.out.println("要解密的字符串:" + object.toString());
            String password = "pass";//模拟解密,需要获得本地的密钥
            setPassWord(password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }*/

    /**
     * 静态变量序列化
     * 序列化时,并不保存静态变量,序列化保存的是对象的状态,静态变量属于类的状态,
     * 因此序列化不保存静态变量
     */
    @org.junit.Test
    public void test1() throws IOException, ClassNotFoundException {
        //初始时,staticVar为5
        ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("result.obj"));
        out.writeObject(new Test());
        out.close();
        //序列化后修改为10
        Test.staticVar=10;
        ObjectInputStream oin = new ObjectInputStream(
                new FileInputStream("result.obj"));
        Test test = (Test) oin.readObject();
        oin.close();
        System.out.println("再读取"+test.staticVar);

    }

    /**
     * 情境:服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,
     * 进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
     * 解决: 在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,
     * 如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
     * 用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
     * 基于这个原理,可以在实际应用中得到使用,用于敏感字段的加密工作
     * RMI 技术是完全基于 Java 序列化技术的,服务器端接口调用所需要的参数对象来至于客户端,它们通过网络相互传输。这就涉及 RMI 的安全传输的问题。
     * 一些敏感的字段,如用户名密码(用户登录时需要对密码进行传输),我们希望对其进行加密,这时,就可以采用本节介绍的方法在客户端对密 码进行加密,服务器端进行解密,确保数据传输的安全性
     */
    @org.junit.Test
    public void test2(){
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            out.writeObject(new Test());
            out.close();

            ObjectInputStream oin = new ObjectInputStream(
                    new FileInputStream("result.obj"));
            Test test = (Test) oin.readObject();
            System.out.println("解密后的字符串:" + test.getPassWord());
            oin.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 序列化存储规则
     * 对同一对象两次写入文件,打印出写入一次对象后的存储大小和写入两次后的存储大小,然后从文件中反序列化出两个对象,
     * 比较这两个对象是否为同一对象。一 般的思维是,两次写入对象,文件大小会变为两倍的大小,反序列化时,由于从文件读取,
     * 生成了两个对象,判断相等时应该是输入 false 才对,但是:我们看到,第二次写入对象时文件只增加了 5 字节,并且两个对象是相等的
     * 解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,
     * 而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。
     * 反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间
     */
    @org.junit.Test
    public void test3(){
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            Test test = new Test();
            //试图将对象两次写入文件
            out.writeObject(test);
            out.flush();
            System.out.println(new File("result.obj").length());
            out.writeObject(test);
            out.close();
            System.out.println(new File("result.obj").length());
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                    "result.obj"));
            //从文件依次读出两个文件
            Test t1 = (Test) oin.readObject();
            Test t2 = (Test) oin.readObject();
            oin.close();
            //判断两个引用是否指向同一个对象
            System.out.println(t1 == t2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 目的是希望将 test 对象两次保存到 result.obj 文件中,写入一次以后修改对象属性值再次保存第二次,
     * 然后从 result.obj 中再依次读出两个对象,输出这两个对象的 i 属性值。
     * 案例代码的目的原本是希望一次性传输对象修改前后的状态
     * 结果:两个输出的都是 1, 原因就是第一次写入对象以后,第二次再试图写的时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,
     * 因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。读者在使用一个文件多次 writeObject 需要特别注意这个问题
     */
    @org.junit.Test
    public void test4(){
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            Test test = new Test();
            test.setI(1);
            //test.i=1;
            out.writeObject(test);
            out.flush();
            //test.i = 2;
            test.setI(2);
            out.writeObject(test);
            out.close();
            ObjectInputStream oin = new ObjectInputStream(
                    new FileInputStream("result.obj"));
            Test t1 = (Test) oin.readObject();
            Test t2 = (Test) oin.readObject();
            System.out.println(t1.i);
            System.out.println(t2.i);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}