zoukankan      html  css  js  c++  java
  • Java 对象的序列化、反序列化

    对象的序列化(Serialize):将内存中的Java对象转换为与平台无关的二进制流(字节序列),然后存储在磁盘文件中,或通过网络传输给另一个网络节点。

    对象的反序列化(Deserialize):获取序列化的二进制流(不管是通过网络,还是通过读取磁盘文件),将之恢复为原来的Java对象。

    要实现对象的序列化,该对象所属的类必须要是可序列化的,即该类必须实现以下2个接口之一:

    • Serializable     这只是一个标记接口,此接口只是表明该类是可序列化的,不用实现任何方法。Java自带的类基本都已implement  Serializable,不用我们操心,我们自定义的类                         要实现序列化,必须要手写implement  Serializable。
    • Externalizable      用于实现自定义序列化

    要通过网络传输的Java对象、要保存到磁盘的Java对象必须要是可序列化的,不然程序会出现异常。

    JavaEE的分布式应用往往要跨平台、跨网络,通过网络传输的Java对象必须要是可序列化的,HttpSession、ServletContext等Java  Web对象都已实现序列化。如果我们要通过网络传输自定义的Java对象,该类(一般是JavaBean)要是可序列化的。通常建议:把所有的JavaBean都写成是可序列化的。

    ObjectOutputStream是一个处理流,提供了void  writeObject(Object  obj)方法用于对象的序列化(对象输出流,输出到文件/网络)。

    ObjectInputStream也是一个处理流,提供了Object  readObject()方法用于反序列化(对象输入流,读取文件/网络中的对象到内存)。

    示例:

     1 //要实现Serializable接口(并不用实现任何方法)
     2 class Student implements Serializable{
     3     private int id;
     4     private String name;
     5     private int age;
     6 
     7     public Student(int id,String name,int age){
     8         this.id=id;
     9         this.name=name;
    10         this.age=age;
    11     }
    12 
    13    public int getId(){
    14         return this.id;
    15    }
    16 
    17    public void setId(int id){
    18         this.id=id;
    19    }
    20 
    21    public String getName(){
    22         return this.name;
    23    }
    24 
    25    public void setName(String name){
    26         this.name=name;
    27    }
    28 
    29    public int getAge(){
    30         return this.age;
    31    }
    32 
    33    public void setAge(int age){
    34         this.age=age;
    35    }
     1  //序列化
     2         ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));  //处理流,要建立在节点流之上
     3         Student zhangsan=new Student(1,"张三",20);
     4         oos.writeObject(zhangsan);   //writeObject(Object obj)用于序列化一个对象(输出到文件/网络节点)
     5 
     6         //反序列化
     7         ObjectInputStream ois=new ObjectInputStream(new FileInputStream("./obj.txt"));
     8         Student student=(Student)ois.readObject();   //返回的是Object类型,所以往往要强制类型转换
     9 
    10         System.out.println("学号:"+student.getId());
    11         System.out.println("姓名:"+student.getName());
    12         System.out.println("年龄:"+student.getAge());

    ObjectInputStream、ObjectOutputStream也包含了其他IO方法,可输入/输出多种类型的数据,即可以字符为单位进行操作,又可以字节为单位进行操作。

    writeObject(Object  obj)一次只能写出一个对象,可多次调用,向同一文件中写入多个对象;

    readObject()一次只能读一个对象,读取一个对象后,指针自动后移,指向下一个对象。读的顺序和写的顺序是一一对应的。

    示例:

     1  //序列化,向同一文件中写入多个对象
     2         ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));  //处理流,要建立在节点流之上
     3         Student zhangsan=new Student(1,"张三",20);
     4         Student lisi=new Student(2,"李四",20);
     5         Student wangwu=new Student(3,"王五",20);
     6         oos.writeObject(zhangsan);  //张三
     7         oos.writeObject(lisi);  //李四
     8         oos.writeObject(wangwu);  //王五
     9 
    10         //反序列化,读取顺序和写入顺序是一致的
    11         ObjectInputStream ois=new ObjectInputStream(new FileInputStream("./obj.txt"));
    12         Student zhangsan1=(Student)ois.readObject();    //张三
    13         Student lisi1=(Student)ois.readObject();  //李四
    14         Student wangwu1=(Student)ois.readObject();  //王五

    如果要序列化的类有父类(直接或者间接),那么这些父类必须要是可序列化的,或者具有无参的构造函数,否则会抛出 InvalidClassException  异常。

    1 class Student extends People implements Serializable{
    2     //......
    3 }

    如果Student要序列化,则有2种方案可选:

    • 其父类People也要是可序列化的(递归)。       这样Student继承自People中的成员才可以序列化输出到文件/网络。
    • 其父类不是可序列化的,但是其父类有无参的构造函数。      这样不会报错,但Student继承自People中的成员不会序列化输出到文件/网络。

    如果该类持有其它类的引用,则引用的类都要是可序列化的,此类才是可序列化的。

    1 class Student implements Serializable {
    2     private int id;
    3     private String name;  //Java自带的类基本都实现了可序列化,不用管
    4     private int age;
    5     private Teacher teacher;    //此类持有一个Teacher类的引用。Teacher类必须要是可序列化的,Student类才是可序列化的。
    6     //......
    7 }

    如果要多次输出同一个对象,则第一次输出的是该对象的字节序列,以后每次输出的是该对象的编号。

    1   ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
    2         Student zhangsan=new Student(1,"张三",20);
    3 
    4         oos.writeObject(zhangsan);   //第一次序列化这个对象,输出此对象的字节序列。假设此对象的编号为1,编号唯一标识此对象。
    5         oos.writeObject(zhangsan);  //再次输出此对象,输出的是此对象的编号,直接输出1。读取此对象时读取的仍是zhangsan这个对象。
    6         oos.writeObject(zhangsan);  //直接输出1。
    7         //类似于c++中的指针,通过编号指向某个已存在对象。减少了重新序列化对象的时间、内存开销。

    如果中间修改了此对象,再次序列化此对象时,修改后的结果不会同步到文件/网络节点中。

     1  ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
     2         Student zhangsan=new Student(1,"张三",20);
     3 
     4         oos.writeObject(zhangsan);   //初次序列化,输出此对象的字节序列。假设编号为1
     5         zhangsan.setName("李四");   //之后修改了此对象
     6         oos.writeObject(zhangsan);  //再次输出此对象,输出的是编号1。1代表编号为1的对象。
     7 
     8         /*
     9         在此对象第一次序列化之后,对此对象做修改,后面再次输出此对象时,都是指向第一次输出的那个对象,做的修改并不会写到文件/网络节点中。
    10         读取的也都是第一次输出的那个对象。
    11 
    12         实际上,序列化一个对象时,JVM会先检查在本次虚拟机中是否已序列化过这个对象,如果已序列化过,直接输出对应的序列化编号,未序列化过才序列化。
    13      JVM是以对象名来区分序列化的是否是同一个对象。对象名相同,JVM就认为序列化的是同一个对象,直接输出该对象的编号,并不判断此对象是否修改过。
    14

    15 修改此对象后,就算我们把此对象序列化到另一个文件中,输出的也是此对象第一次序列化时的编号,修改并不会同步到这个文件中。
    16 */

    Java9新增了过滤功能,读取对象时,可以先检查该对象是否满足指定的要求,满足才允许恢复,否则拒绝恢复。

     1  ObjectInputStream ois=new ObjectInputStream(new FileInputStream("obj.txt"));
     2         /*
     3         参数是一个ObjectInputFilter接口的对象,函数式接口,只需实现checkInput()方法。
     4         参数info是FilterInfo类的对象。
     5 
     6         返回值要是预定义的常量:
     7         Status.ALLOWED  允许恢复
     8         Status.REJECTED  拒绝恢复
     9         Status.UNDECIDED  未决定,将继续执行检查
    10          */
    11         ois.setObjectInputFilter((info)->{
    12             ObjectInputFilter serialFilter=ObjectInputFilter.Config.getSerialFilter();   //获取默认的输入过滤器
    13             if(serialFilter!=null){
    14                 //先使用默认的过滤器进行检查
    15                 ObjectInputFilter.Status status=serialFilter.checkInput(info);
    16                 //如果已知道结果
    17                 if (status!= ObjectInputFilter.Status.UNDECIDED)
    18                     return status;
    19             }
    20             //执行到此,就说明结果是Status.UNDECIDED,还不知道结果,要继续检查
    21             //如果引用不唯一,说明数据被污染了,不允许恢复
    22             if(info.references()!=1)
    23                 return ObjectInputFilter.Status.REJECTED;
    24             //是指定的类的对象(Student),才允许恢复(执行反序列化)
    25             if(info.serialClass()!=null && info.serialClass() != Student.class)
    26                 return ObjectInputFilter.Status.ALLOWED;
    27             //执行到此,说明不是指定类的对象,不允许恢复。
    28             return ObjectInputFilter.Status.REJECTED;
    29         });
    30         //这样恢复的对象都是唯一的Student对象
    31         //...........
  • 相关阅读:
    solrcloud
    nginx代理服务器3--高可用(keepalived)
    Nginx反向代理1--基本介绍-虚拟主机
    Nginx反向代理2--配置文件配置
    C/S与B/S区别
    数据类型转换(客户端与服务器端)
    SYN Cookie的原理和实现
    ubuntu 18.04 配置 rc.local
    Summary Checklist for Run-Time Kubernetes Security
    形意拳内功心法
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/10921072.html
Copyright © 2011-2022 走看看