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新增了过滤功能,读取对象时,会先检查该对象是否满足指定的要求,满足才允许恢复,否则拒绝恢复。