zoukankan      html  css  js  c++  java
  • (JAVA)从零开始之--对象输入输出流ObjectInputStream、ObjectOutputStream(对象序列化与反序列化)

    对象的输入输出流 : 主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了
      对象的输出流ObjectOutputStream
      对象的输入流:  ObjectInputStream

    使用:

    对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。(查看源码可得知:Serializable接口没有任何的方法,只是作为一个标识接口存在)。

    1、将User类的对象序列化

     1 class User implements Serializable{//必须实现Serializable接口
     2     String uid;
     3     String pwd;
     4     public User(String _uid,String _pwd){
     5         this.uid = _uid;
     6         this.pwd = _pwd;
     7     }
     8     @Override
     9     public String toString() {
    10         return "账号:"+this.uid+" 密码:"+this.pwd;
    11     }
    12 }
    13 
    14 public class Demo1 {
    15 
    16     public static void main(String[] args) throws IOException {
    17         //假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件
    18         File f = new File("F:\obj.txt");
    19         writeObjec(f);
    20         System.out.println("OK");
    21     }
    22     
    23     //定义方法把对象的信息写到硬盘上------>对象的序列化。
    24     public static void writeObjec(File f) throws IOException{
    25         FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象
    26         ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    27         objectOutputStream.writeObject(new User("酒香逢","123"));
    28         //最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可
    29         objectOutputStream.close();
    30     }
    31 }

    运行程序得到记事本中存入的信息:可见已经序列化到记事本中

    2、将序列化到记事本的内容反序列化

     1 public class Demo1 {
     2 
     3     public static void main(String[] args) throws IOException, ClassNotFoundException {
     4         //假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件
     5         File f = new File("F:\obj.txt");
     6         //writeObjec(f);
     7         readObject(f);
     8         System.out.println("OK");
     9     }
    10     
    11     //定义方法把对象的信息写到硬盘上------>对象的序列化。
    12     public static void writeObjec(File f) throws IOException{
    13         FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象
    14         ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    15         objectOutputStream.writeObject(new User("酒香逢","123"));
    16         //最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可
    17         objectOutputStream.close();
    18     }
    19     //把文件中的对象信息读取出来-------->对象的反序列化
    20     public static void readObject(File f) throws IOException, ClassNotFoundException{
    21         FileInputStream inputStream = new FileInputStream(f);//创建文件字节输出流对象
    22         ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
    23         User user = (User)objectInputStream.readObject();
    24         System.out.println(user);
    25     }
    26 }

    运行代码得到的结果:

    账号:酒香逢 密码:123
    OK

    但是,如果这时候这个obj.txt是我们项目中一个文件,而项目到后期在原来User类的基础上添加成员变量String userName;

     1 class User implements Serializable{//必须实现Serializable接口
     2     String uid;
     3     String pwd;
     4     String userName="名字";//新添加的成员变量
     5     public User(String _uid,String _pwd){
     6         this.uid = _uid;
     7         this.pwd = _pwd;
     8     }
     9     @Override
    10     public String toString() {
    11         return "账号:"+this.uid+" 密码:"+this.pwd;
    12     }
    13 }

    这时候如果我们再反序列化,则会引发下面的异常:

    Exception in thread "main" java.io.InvalidClassException: xuliehua.User; local class incompatible: stream classdesc serialVersionUID = 2161776237447595412, local class serialVersionUID = -3634244984882257127
      at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604)
      at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601)
      at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
      at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
      at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
      at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
      at xuliehua.Demo1.readObject(Demo1.java:48)
      at xuliehua.Demo1.main(Demo1.java:32)

    异常信息解读:

    serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是JVM(JAVA虚拟界)通过一个类的类名、成员、包名、工程名算出的一个数字。而这时候序列化文件中记录的serialVersionUID与项目中的不一致,即找不到对应的类来反序列化。

    3、如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
    在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。

    去掉刚才添加的成员变量userName;,并且在User类中指定一个serialVersionUID 

     1 class User implements Serializable{//必须实现Serializable接口
     2     
     3     private static final long serialVersionUID = 1L;
     4     String uid;
     5     String pwd;
     6     //String userName="名字";//新添加的成员变量
     7     public User(String _uid,String _pwd){
     8         this.uid = _uid;
     9         this.pwd = _pwd;
    10     }
    11     @Override
    12     public String toString() {
    13         return "账号:"+this.uid+" 密码:"+this.pwd;
    14     }
    15 }

    重新序列化到obj.txt文件中,然后再类中再将userName添加回来(将上面User类中userName字段解注释),再一次执行反序列化操作,执行的结果跟之前反序列化的结果是一致的。可见这样解决后我们后期修改类也是可行的。

    4、如果在User类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。

     1 class Address implements Serializable{
     2     String country;
     3     String city;
     4 }
     5 
     6 class User implements Serializable{//必须实现Serializable接口
     7     
     8     private static final long serialVersionUID = 1L;
     9     String uid;
    10     String pwd;
    11     String userName="名字";//新添加的成员变量
    12     Address address;//成员变量为Address
    13     public User(String _uid,String _pwd){
    14         this.uid = _uid;
    15         this.pwd = _pwd;
    16     }
    17     @Override
    18     public String toString() {
    19         return "账号:"+this.uid+" 密码:"+this.pwd;
    20     }
    21 }

    5、最后再提一下关键字transient关键字,当你不想要某些字段序列化时候,可以用transient关键字修饰

     1 class User implements Serializable{//必须实现Serializable接口
     2     
     3     private static final long serialVersionUID = 1L;
     4     String uid;
     5     String pwd;
     6     transient String userName="名字";//新添加的成员变量//添加关键字transient后,序列化时忽略
     7     Address address;//成员变量为Address
     8     public User(String _uid,String _pwd){
     9         this.uid = _uid;
    10         this.pwd = _pwd;
    11     }
    12     @Override
    13     public String toString() {
    14         return "账号:"+this.uid+" 密码:"+this.pwd;
    15     }
    16 }

    最后总结一下对象输入输出流使用时需要注意:

    1. 如果对象需要被写出到文件上,那么对象所属的类必须要实现Serializable接口。 Serializable接口没有任何的方法,是一个标识接口而已。
    2. 对象的反序列化创建对象的时候并不会调用到构造方法的、(这点文中没有说到,想要验证的同学在构造方法后面加一句System.out.println("构造方法执行吗?");,实际上构造方法是不执行的,自然这句话也没有输出了)
    3. serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。
    4. 使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID
    进行对比,如果这两个id不一致,反序列则失败。
    5. 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
    在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。
    6. 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰。
    7. 如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口。

  • 相关阅读:
    HDU 4348 To the moon(可持久化线段树)
    HDU 5875 Function 大连网络赛 线段树
    HDU 5877 2016大连网络赛 Weak Pair(树状数组,线段树,动态开点,启发式合并,可持久化线段树)
    HDU 5876 大连网络赛 Sparse Graph
    HDU 5701 中位数计数 百度之星初赛
    CodeForces 708B Recover the String
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    ASP.NET生成验证码
    ASP.NET生成验证码
    ASP.NET生成验证码
  • 原文地址:https://www.cnblogs.com/fnz0/p/5410856.html
Copyright © 2011-2022 走看看