24 Java学习之对象序列化和反序列化
Posted Hermioner
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了24 Java学习之对象序列化和反序列化相关的知识,希望对你有一定的参考价值。
一. 序列化和反序列化的概念
对象序列化:把对象转换为字节序列的过程
对象反序列化:把字节序列恢复为对象的过程
1. 为何要进行序列化
我们知道当虚拟机停止运行之后,内存中的对象就会消失。在很多应用中,需要对某些对象进行序列化,让他们离开内存空间,进入物理硬盘,便于长期保存。例如,最常见的是WEB服务器中的Session对象,当有10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些Session先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
即对象序列化主要有两种用途:
(1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
(2)在网络上传送对象的字节序列。
二. 如何序列化
1. 前提条件
如果要让每个对象支持序列化机制,比如让它的类是可序列化的,则该类必须实现如下两个接口之一:
- Serializable
- Extmalizable:该接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅仅实现Serializable接口的类可以采用默认的序列化方式
2. 相关API
有两个类常常用于序列化和反序列化:java.io.ObjectOutputStream和java.io.ObjectInputStream
(1)java.io.ObjectOutputStream
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
(2)java.io.ObjectInputStream
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把他们反序列化为一个对象,并将其返回。
3. 对象序列化步骤
(1)创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流;
(2)通过对象输出流的writeObject()方法写对象
4. 对象反序列化的步骤
(1)创建一个对象输入流,它可以包装一个其它类型的源输入流,如文件输入流;
(2)通过对象输入流的readObject()方法读取对象。
5. 重要原则
- Serializable是一个标示性接口,接口中没有定义任何的方法或字段,仅用于标示可序列化的语义。要序列化必须实现,否则异常
- 静态变量和成员方法不可序列化。
- 一个类要能被序列化,该类中的所有引用对象也必须是可以被序列化的。否则整个序列化操作将会失败,并且会抛出一个NotSerializableException,除非我们将不可序列化的引用标记为transient。
- 声明成transient的变量不被序列化工具存储,同样,static变量也不被存储。
三. 使用举例
1. 将一个对象序列化之后存储到文件中
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable{ 6 private static final long serialVersionUID = 1L; 7 public String name; 8 public int age; 9 10 public Person(String name, int age) { 11 this.name = name; 12 this.age = age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 public int getAge() { 24 return age; 25 } 26 27 public void setAge(int age) { 28 this.age = age; 29 } 30 31 }
1 package com.test.a; 2 3 import java.io.FileNotFoundException; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.ObjectOutputStream; 7 8 9 public class Test { 10 public static void main(String args[]) throws FileNotFoundException, IOException{ 11 Person person=new Person("zhangsan", 23); 12 String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt"; 13 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 14 objectOutputStream.writeObject(person); 15 objectOutputStream.close(); 16 } 17 }
说明:上面是执行结果。对象序列化之后,写入的是一个二进制文件,所以打开乱码是正常现象,不过透过乱码我们还是能够知道就是我们序列化的哪个对象。Person对象实现了Serializable接口,这个接口没有任何方法需要被实现,只是一个标记接口,表示这个类的对象可以被序列化,如果没有明确写了实现这个接口,就会抛出异常。
2. 从文件中反序列化对象
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan", 23); 14 String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person2=(Person) objectInputStream.readObject(); 21 System.out.println(person2.getName()); 22 System.out.println(person2.getAge()); 23 objectInputStream.close(); 24 } 25 } 26 27 zhangsan 28 23
说明:在调用readObject()方法的时候,有一个强转的动作。所以在反序列化时,要提供java对象所属类的class文件。
3. 多个对象的反序列化
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan", 23); 14 Person person2=new Person("lisi", 13); 15 String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt"; 16 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 17 objectOutputStream.writeObject(person); 18 objectOutputStream.writeObject(person2); 19 objectOutputStream.close(); 20 21 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 22 Person person4=(Person) objectInputStream.readObject(); 23 System.out.println(person4.getName()); 24 System.out.println(person4.getAge()); 25 Person person5=(Person) objectInputStream.readObject(); 26 System.out.println(person5.getName()); 27 System.out.println(person5.getAge()); 28 objectInputStream.close(); 29 } 30 } 31 32 33 zhangsan 34 23 35 lisi 36 13
说明:如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取。
4. 对象引用的序列化
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 private static final long serialVersionUID = 1L; 7 public String name; 8 public int age; 9 public Man man; 10 11 public Person(String name, int age, Man man) { 12 this.name = name; 13 this.age = age; 14 this.man = man; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public int getAge() { 26 return age; 27 } 28 29 public void setAge(int age) { 30 this.age = age; 31 } 32 33 public Man getMan() { 34 return man; 35 } 36 37 public void setMan(Man man) { 38 this.man = man; 39 } 40 41 }
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public abstract class Man implements Serializable{ //必须实现Serializable 6 public abstract void getInfo(); 7 } 8 9 10 package com.test.a; 11 12 public class Femal extends Man{ 13 14 @Override 15 public void getInfo() { 16 System.out.println("Femal"); 17 18 } 19 20 } 21 22 package com.test.a; 23 24 public class Male extends Man{ 25 26 @Override 27 public void getInfo() { 28 System.out.println("male"); 29 30 } 31 32 }
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan",24, new Femal()); 14 String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4.getName()); 22 System.out.println(person4.getAge()); 23 Femal man=(Femal) person4.getMan(); 24 man.getInfo(); 25 objectInputStream.close(); 26 } 27 }
1 zhangsan 2 24 3 Femal
说明:上面介绍对象的成员变量都是基本数据类型,如果对象的成员变量是引用类型,这个引用类型的成员变量必须也是可序列化的,否则拥有该类型成员变量的类的对象不可序列化。
四. serialVersionUID
serialVersionUID:字面意思是序列化的版本号,凡是实现了Seriallizable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID = 1L;
实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现如下的警告提示
看上面的提示信息,可以知道有两种方式来生成serial version ID
(1)采用默认方式
private static final long serialVersionUID = 1L;
这种方式生成的serialVersionUID是1L.
(2)采用第二种
private static final long serialVersionUID = -6587084022709540081L;
这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的。
1. serialVersionUID的作用
(1)假设如下Person对象进行序列化和反序列化----没有加入serialVersionUID
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 7 public String name; 8 public int age; 9 10 11 public Person(String name,int age) { 12 this.name=name; 13 this.age=age; 14 } 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 24 public int getAge() { 25 return age; 26 } 27 28 public void setAge(int age) { 29 this.age = age; 30 } 31 }
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan",24); 14 String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4); 22 System.out.println(person4.getName()); 23 System.out.println(person4.getAge()); 24 25 objectInputStream.close(); 26 } 27 } 28 29 com.test.a.Person@448139f0 30 zhangsan 31 24
com.test.a.Person@448139f0
zhangsan
24
(2)修改Person,添加一个新的属性
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 7 public String name; 8 public int age; 9 public String sex;/////new added 10 11 12 public Person(String name,int age) { 13 this.name=name; 14 this.age=age; 15 } 16 17 public Person(String name,int age,String sex)/////new added 18 { 19 this.name=name; 20 this.age=age; 21 this.sex=sex; 22 } 23 24 public String getName() { 25 return name; 26 } 27 28 public void setName(String name) { 29 this.name = name; 30 } 31 32 public int getAge() { 33 return age; 34 } 35 36 public void setAge(int age) { 37 this.age = age; 38 } 39 }
此时只执行反序列化,(因为生成的对象还是调用的之前两个参数的哪个对象)
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 // Person person=new Person("zhangsan",24); 14 String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt"; 15 // ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 // objectOutputStream.writeObject(person); 17 // objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4); 22 System.out.println(person4.getName()); 23 System.out.println(person4.getAge()); 24 25 objectInputStream.close(); 26 } 27 }
1 Exception in thread "main" java.io.InvalidClassException: com.test.a.Person; local class incompatible: stream classdesc serialVersionUID = 4647091331428092166, local class serialVersionUID = -7232940355925514760 2 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687) 3 at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883) 4 at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749) 5 at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040) 6 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) 7 at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) 8 at com.test.a.Test.main(Test.java:20)
说明:此时用的还是之前序列化的结果进行反序列化,只是此次反序列化的之前,还修改了Person。因此会造成不兼容的现象。根据上面错误提示我们知道:文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在Test例子中,没有指定Person类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显
以上是关于24 Java学习之对象序列化和反序列化的主要内容,如果未能解决你的问题,请参考以下文章