(JAVA)从零开始之--对象输入输出流ObjectInputStream、ObjectOutputStream(对象序列化与反序列化)
2016-04-20 00:10
731 查看
对象的输入输出流 : 主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了
对象的输出流: ObjectOutputStream
对象的输入流: ObjectInputStream
使用:
对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。(查看源码可得知:Serializable接口没有任何的方法,只是作为一个标识接口存在)。
1、将User类的对象序列化
运行程序得到记事本中存入的信息:可见已经序列化到记事本中
2、将序列化到记事本的内容反序列化
运行代码得到的结果:
账号:酒香逢 密码:123
OK
但是,如果这时候这个obj.txt是我们项目中一个文件,而项目到后期在原来User类的基础上添加成员变量String userName;
这时候如果我们再反序列化,则会引发下面的异常:
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
重新序列化到obj.txt文件中,然后再类中再将userName添加回来(将上面User类中userName字段解注释),再一次执行反序列化操作,执行的结果跟之前反序列化的结果是一致的。可见这样解决后我们后期修改类也是可行的。
4、如果在User类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。
5、最后再提一下关键字transient关键字,当你不想要某些字段序列化时候,可以用transient关键字修饰
最后总结一下对象输入输出流使用时需要注意:
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接口。
对象的输出流: ObjectOutputStream
对象的输入流: ObjectInputStream
使用:
对象的输出流将指定的对象写入到文件的过程,就是将对象序列化的过程,对象的输入流将指定序列化好的文件读出来的过程,就是对象反序列化的过程。既然对象的输出流将对象写入到文件中称之为对象的序列化,那么可想而知对象所对应的class必须要实现Serializable接口。(查看源码可得知:Serializable接口没有任何的方法,只是作为一个标识接口存在)。
1、将User类的对象序列化
class User implements Serializable{//必须实现Serializable接口 String uid; String pwd; public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; } @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd; } } public class Demo1 { public static void main(String[] args) throws IOException { //假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件 File f = new File("F:\\obj.txt"); writeObjec(f); System.out.println("OK"); } //定义方法把对象的信息写到硬盘上------>对象的序列化。 public static void writeObjec(File f) throws IOException{ FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(new User("酒香逢","123")); //最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可 objectOutputStream.close(); } }
运行程序得到记事本中存入的信息:可见已经序列化到记事本中
2、将序列化到记事本的内容反序列化
public class Demo1 { public static void main(String[] args) throws IOException, ClassNotFoundException { //假设将对象信息写入到obj.txt文件中,事先已经在硬盘中建立了一个obj.txt文件 File f = new File("F:\\obj.txt"); //writeObjec(f); readObject(f); System.out.println("OK"); } //定义方法把对象的信息写到硬盘上------>对象的序列化。 public static void writeObjec(File f) throws IOException{ FileOutputStream outputStream = new FileOutputStream(f);//创建文件字节输出流对象 ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(new User("酒香逢","123")); //最后记得关闭资源,objectOutputStream.close()内部已经将outputStream对象资源释放了,所以只需要关闭objectOutputStream即可 objectOutputStream.close(); } //把文件中的对象信息读取出来-------->对象的反序列化 public static void readObject(File f) throws IOException, ClassNotFoundException{ FileInputStream inputStream = new FileInputStream(f);//创建文件字节输出流对象 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); User user = (User)objectInputStream.readObject(); System.out.println(user); } }
运行代码得到的结果:
账号:酒香逢 密码:123
OK
但是,如果这时候这个obj.txt是我们项目中一个文件,而项目到后期在原来User类的基础上添加成员变量String userName;
class User implements Serializable{//必须实现Serializable接口 String uid; String pwd; String userName="名字";//新添加的成员变量 public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; } @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd; } }
这时候如果我们再反序列化,则会引发下面的异常:
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
class User implements Serializable{//必须实现Serializable接口 private static final long serialVersionUID = 1L; String uid; String pwd; //String userName="名字";//新添加的成员变量 public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; } @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd; } }
重新序列化到obj.txt文件中,然后再类中再将userName添加回来(将上面User类中userName字段解注释),再一次执行反序列化操作,执行的结果跟之前反序列化的结果是一致的。可见这样解决后我们后期修改类也是可行的。
4、如果在User类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。
class Address implements Serializable{ String country; String city; } class User implements Serializable{//必须实现Serializable接口 private static final long serialVersionUID = 1L; String uid; String pwd; String userName="名字";//新添加的成员变量 Address address;//成员变量为Address public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; } @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd; } }
5、最后再提一下关键字transient关键字,当你不想要某些字段序列化时候,可以用transient关键字修饰
class User implements Serializable{//必须实现Serializable接口 private static final long serialVersionUID = 1L; String uid; String pwd; transient String userName="名字";//新添加的成员变量//添加关键字transient后,序列化时忽略 Address address;//成员变量为Address public User(String _uid,String _pwd){ this.uid = _uid; this.pwd = _pwd; } @Override public String toString() { return "账号:"+this.uid+" 密码:"+this.pwd; } }
最后总结一下对象输入输出流使用时需要注意:
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接口。
相关文章推荐
- Objective-C初学:属性
- OBJ-C简介部分学习笔记
- android ObjectAnimator基本应用
- 使用ObjectARX进行AutoCAD的二次开发 ——新建工程
- objective-C基础----字典,集合
- 自动滚动的 tableView 和高度自适应的 textView
- JavaScript之引用类型Object类型
- Objective-C 字典的基本用法(NSDictionary和NSMutableDictionary)
- JsonObject和JsonArray的使用
- Effective Object C 2.0——对象、消息和运行期
- 为什么JavaScript里面typeof(null)的值是"object"?
- css z-index之object flash修正
- 详解Objective-C runtime
- [HSON : HC Script Object Notation] Finish Build 1 On GitHub
- Objective-C Runtime能做什么?
- 1.一对多双向关联(班级对学生):Object,hbm
- 1.一对多单向关联(班级对学生):Object,hbm
- object-c URL
- Type Object——类型对象
- 从Object对象中封装了其它对象,怎样取出呢?(Hibernate查询结果处理)