zoukankan      html  css  js  c++  java
  • Java序列化技术

    什么是序列化

    序列化,可以简单地理解为轻量级的持久化(persistence),而且是针对对象实例(instance)的持久化。

    整个序列化的过程就是:将对象信息转化为二进制数据序列,里面保存了对象的类型信息、引用类型信息、对象状态信息(我理解为成员变量的值)。而反序列化,就是根据流(stream)中读取的字节序列信息,重新包装一个对象,他的状态和序列化之前的对象一样。

    序列化的整个过程由虚拟机实现,因此即使在不同的虚拟机上,仍然可以做到对象还原。因此,序列化主要用于1.RMI 2.JavaBean的状态保存。

    示例代码——使用序列化存取对象

    • 对象必须实现Serializable接口,这是标记接口,没有任何方法,但是必须实现这个接口才能使用序列化机制
    • 通过ObjectOutputStreamwriteObject(...)写入对象
    • 通过ObjectInputStreamreadObject(...)读取对象

    序列化工具类

    /**
     * @author luzj
     * @description:
     * 1. 处理对象的序列化以及反序列化的工具
     * 2. T:待序列化对象的对象的类型
     * @date 2018/3/2
     */
    public class SerialUtil<T> {
    
        private String path;
    
        public String getPath() {
            return path;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    
        public SerialUtil(String path) {
            this.path = path;
        }
    
        /**
         * 序列化对象
         * @param obj
         */
        public void serialObj(T obj) {
            FileOutputStream outputStream = null;
            ObjectOutputStream objectOutputStream = null;
            try {
                outputStream = new FileOutputStream(this.getPath());
                objectOutputStream = new ObjectOutputStream(outputStream);
                objectOutputStream.writeObject(obj);
                objectOutputStream.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                System.out.println(obj);
                try {
                    if (objectOutputStream != null) {
                        objectOutputStream.close();
                    }
                    if (outputStream != null)
                        outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 反序列化对象
         */
        public void deSerialObj() {
            FileInputStream in = null;
            ObjectInputStream objectInputStream = null;
            T obj = null;
            try {
                in = new FileInputStream(this.getPath());
                objectInputStream = new ObjectInputStream(in);
                obj = (T) objectInputStream.readObject();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (objectInputStream != null)
                        objectInputStream.close();
                    if (in != null)
                        in.close();
                    if (obj != null)
                        System.out.println(obj);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    用于序列化的类

    public class MySerial implements Serializable {
        private static final long serialVersionUID = 1L;
        public String name;
        public int code;
        //transient变量不会参与序列化
        public transient int SNN;
    
        public MySerial(String name) {
            this.name = name;
        }
    
        public MySerial(String name, int code) {
            this.name = name;
            this.code = code;
            this.SNN = 40;
        }
    
        @Override
        public String toString() {
            return "name:" + name + "code:" + code + "SNN:" + SNN + ",hashcode:" + this.hashCode();
        }
    }
    

    测试

    String path = "src/main/resource/myserial.ser";//本地存储地址
    SerialUtil<MySerial> serialSerialUtil = new SerialUtil<>(path);
    MySerial mySerial = new MySerial("ada wong", 233);
    serialSerialUtil.serialObj(mySerial);
    System.err.println("=======================================");
    serialSerialUtil.deSerialObj();
    

    输出结果

    为什么会抛出ClassNotFoundException

    我们知道,序列化很多时候会用于两个虚拟机之间传输对象。这个过程或许通过磁盘IO,或许通过网络,都没关系。

    在反序列化的时候,虚拟机或重组序列化对象,此时如果本地虚拟机如果找不到对应类,就会报出ClassNotFoundException,因此在调用readObject()时需要捕获该异常。

    比如上面的Myserial类,本地要一个,远程主机也必须有一个。否则就会报异常。

    serialVersionUID的作用

    每一个序列化对象会有一个serialVersionUID,那么他的作用是什么?我们知道,序列化很多时候会用于RMI,本地一个类,远程也必须有一个同功能的类。

    可是,如果两个类的serialVersionUID不一样,就会报出反序列化失败的提示信息。

    因此,互相传输的两个虚拟机的类UID必须相同

    继承关系中的序列化

    想象这样一种场景,我们序列化的对象有一个父类,但是父类没有实现Serialization接口,这时候会出现什么情况呢?

    答案是,子类从父类中继承的属性不会被序列化。

    示例代码

    // 父类,未序列化
    public class Person{
        public String name;
        public int code;
    
        @Override
        public String toString() {
            return "name:"+name+",code:"+code+",hashcode:"+this.hashCode();
        }
    }
    
    //子类,序列化
    public class Nancy extends Person implements Serializable {
        public int age;
    
        @Override
        public String toString() {
            return "name:"+name+",code:"+code+",age:"+age+",hashcode"+this.hashCode();
        }
    }
    
    //测试父子序列化Nancy
    String path = "src/main/resource/nancy.ser";
    SerialUtil<Nancy> serialUtil = new SerialUtil<>(path);
    
    Nancy nancy = new Nancy();
    nancy.age = 123;
    nancy.name= "nancy";
    nancy.code = 213;
    
    serialUtil.serialObj(nancy);
    System.err.println("============================================");
    serialUtil.deSerialObj();
    

    测试结果

    很容易看到,父类的属性值并没有很好还原,只是被重新初始化了。

    另外补充一下,如果一个属性被transient修饰,也不会参与序列化。

    控制序列化域反序列化的过程

    我刚喜欢称他为序列化的前处理。

    首先,我们必须在序列化对象里面添加两个方法:

    private void readObject(ObjectInputStream inputStream)
    private void writeObject(ObjectOutputStream objectOutputStream)
    

    严格记住这两个方法签名,必须按照格式写。让人困惑的是,这两个方法不是定义在接口里,还是私有方法,怎么调用呢?实际上他是根据反射搜索出方法,在调用。

    这里对参考文档的示例代码做一些改进,使看起来更加的形象。

    示例代码

        /**
         * 1.对序列化过程控制
         * 2.加密某个字段
         * @param objectOutputStream
         */
        private void writeObject(ObjectOutputStream objectOutputStream) {
            System.err.println("序列化:");
            ObjectOutputStream.PutField putField;
            try {
                putField = objectOutputStream.putFields();
                System.out.println("原密码是:" + this.password);
                password = XOREncrypt.xOREnc(password);
                putField.put("password", password);
                System.out.println("加密后的密码:" + password);
                objectOutputStream.writeFields();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 1.反序列化控制过程
         * 2.解密password
         * @param inputStream
         */
        private void readObject(ObjectInputStream inputStream) {
             try {
               // inputStream.defaultReadObject();
                System.err.println("反序列化:");
                ObjectInputStream.GetField getField;
                getField = inputStream.readFields();
                password = (String) getField.get("password", "");
                System.out.println("加密密码:" + password);
                password = XOREncrypt.xOREnc(password);
                System.out.println("解密密码:" + password);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        
    //测试
     String path = "src/main/resource/encrypt.ser";
    String password = "ada wong is my wife";
    SerialUtil<EncryptSerial> serialSerialUtil = new SerialUtil<>(path);
    EncryptSerial encryptSerial = new EncryptSerial(password);
    
    serialSerialUtil.serialObj(encryptSerial);
    System.out.println("============================================");
    serialSerialUtil.deSerialObj();
    
    • readObject(...)控制反序列化过程
    • writeObject(...)控制序列化过程
    • XOREncrypt是我个人写的加密工具类,读者完全可以实现一个自己的工具类
    • 这两个方法完全取代了原来的序列化处理机制,如果在执行自定义序列化处理方法前,想执行默认的序列化机制,可以加入一行代码inputStream.defaultReadObject()即可。

    测试结果

    代码详情

    本文代码地址

    参考文章

    深入理解JAVA序列化

    Java - Serialization

    《java编程思想第四版》

  • 相关阅读:
    css选择器中:first-child与:first-of-type的区别
    Chrome 快捷键
    notepad++ html格式化
    Linux VFS的主要的数据结构
    Linux根文件系统介绍
    Linux文件系统测试工具
    p​o​s​t​m​a​r​k​使​用
    虚拟文件系统
    linux文件系统初始化过程(6)---执行init程序
    linux文件系统初始化过程(4)---加载initrd(中)
  • 原文地址:https://www.cnblogs.com/Franken-Fran/p/java_serialization.html
Copyright © 2011-2022 走看看