Java对象序列化

Java对象序列化

1、什么是序列化

Java序列化是指把Java对象转换为字节序列的过程;而反序列化是指把字节序列恢复为Java对象的过程。从而实现网络传输、本地存储等需求;

  • 一般Java对象的生命周期比Java虚拟机短,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库;

  • 在多个项目进行RPC(Remote Procedure Call)调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术;

2、如何实现序列化

将被序列化的类实现Serializable或Externalizable接口即可;在实现Serializable接口的默认情况下,Java会自动将非transient(短暂的,临时的)关键字修饰的属性序列化到指定文件中去;

3、Serializable接口示例

 1 //JavaBean
 2   public class StudentBean implements Serializable{
 3       
 4       private String code;
 5       private String name ;
 6       private Integer age ;
 7       private List<Score> scores  ;
 8       // get/set/toString ...
 9 10   }
11   public class Score implements Serializable {
12       
13       private Integer code ;
14       private String name;
15       private Integer score ;
16       // get/set/toString ...
17   }
18   //SerializableUtils
19   public class SerializableUtils {
20       
21       /**
22        * 序列化
23        * 
24        * @param obj   实现了Serializable接口的类
25        * @param fileName 文件输出路径
26        * @throws IOException
27        */
28       public static void serialize(Object obj, String fileName)
29               throws IOException {
30    
31           FileOutputStream fos = new FileOutputStream(fileName);
32           BufferedOutputStream bos = new BufferedOutputStream(fos);
33           ObjectOutputStream oos = new ObjectOutputStream(bos);
34           oos.writeObject(obj);
35           oos.close();
36       }
37       
38       /**
39        * 反序列化
40        * 
41        * @param fileName  反序列化时读取的文件路径
42        * @return
43        * @throws IOException
44        * @throws ClassNotFoundException
45        */
46       public static Object deserialize(String fileName) throws IOException, ClassNotFoundException {
47           FileInputStream fis = new FileInputStream(fileName);
48           BufferedInputStream bis = new BufferedInputStream(fis);
49           ObjectInputStream ois = new ObjectInputStream(bis);
50           Object obj = ois.readObject();
51           ois.close();
52           return obj;
53       }
54   }
55   // TestClass
56   public class MyTest {
57 58       private StudentBean stu;
59 60       @Before
61       public void before() {
62           stu = new StudentBean();
63           stu.setAge(18);
64           stu.setCode("101");
65           stu.setName("张三");
66           Score s1 = new Score(101, "高等数学", 100);
67           Score s2 = new Score(102, "计算机", 100);
68           Score s3 = new Score(103, "大学物理", 100);
69           List<Score> list = new ArrayList<Score>();
70           list.add(s1);
71           list.add(s2);
72           list.add(s3);
73           stu.setScores(list);
74       }
75 76       @Test
77       public void Test() throws IOException, ClassNotFoundException {
78           // 序列化
79           SerializableUtils.serialize(stu, "student.txt");
80 81           // 反序列化
82           StudentBean student = (StudentBean) SerializableUtils.deserialize("student.txt");
83           System.out.println(student.getName());
84           for (Score s : student.getScores()) {
85               System.out.println(s);
86           }
87       }
88   }

4、serialVersionUID

  • 实现Serializable接口之后可选择添加一个serialVersionUID 属性作为该类的一个序列化版本号,该编码可选择自动生成或者自定义编辑,其主要作用是为当前类添加一个版本标识,UID不变则认为类的内容没变。反序列化时会判断UID是否未发生变化,若序列化后修改了此UID值,则会导致反序列化失败 ,抛出InvalidClassException异常;

  • 不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化;

5、Externalizable接口示例

Externalizable(可外部化的)继承自Serializable,须手动实现writeExternal以及readExternal方法;

 1 //修改StudentBean
 2   public class StudentBean implements Externalizable{
 3  4       private String code;
 5       private String name ;
 6       private Integer age ;
 7       private List<Score> scores  ;
 8  9       /*
10        * 自定义序列化规则
11        */
12       @Override
13       public void writeExternal(ObjectOutput out) throws IOException {
14           out.writeObject(code);
15           out.writeObject(name);
16           out.writeInt(age);
17           out.writeObject(scores);
18       }
19 20       /*
21        * 自定义反序列化规则
22        */
23       @Override
24       public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
25           
26           code = (String)in.readObject();
27           name = (String)in.readObject();
28           age = in.readInt();
29           scores = (List<Score>)in.readObject();
30       }
31       // get/set/toString
32   }

6、部分属性序列化

6.1 transient关键字

transient关键字,在实现Serializable接口情况下,只需要对不需要序列化的属性使用transient关键字进行修饰即可实现在序列化时忽略该字段;

1   private transient String code

6.2 添加writeObject和readObject方法

添加writeObject和readObject方法,在实现Serializable接口的情况下添加如下两个方法,自定义选择序列化哪些字段,但注意若该字段没有参加序列化,则反序列化时亦不能参与;

defaultWriteObject()会执行默认序列化操作即序列化所有非static、transient属性;transient关键字修饰的属性可以用writeObject()方式实现序列化;

原理:调用ObjectOutputStream中writeObject方法时会检查序列化对象是否实现了自己的writeObject方法,如果是则跳过常规序列化转流程转而调用writerObject方法实现序列化readObject同理;

 1       private Integer code ;
 2       private transient String name;
 3       private Integer score ;
 4  5       //序列化
 6       private void writeObject(ObjectOutputStream oos) throws IOException {
 7           oos.defaultWriteObject();
 8           oos.writeObject(name);
 9       }
10       //反序列化
11       private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
12           ois.defaultReadObject();
13           name = (String)ois.readObject();
14       }

6.3 修改writeExternal和readExternal方法

修改writeExternal和readExternal方法,原理同上,且序列化与反序列化属性须一致;

 1 /*
 2        * 自定义序列化规则
 3        */
 4       @Override
 5       public void writeExternal(ObjectOutput out) throws IOException {
 6           //out.writeObject(code);
 7           out.writeObject(name);
 8           out.writeInt(age);
 9           out.writeObject(scores);
10       }
11 12       /*
13        * 自定义反序列化规则
14        */
15       @Override
16       public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
17           
18           //code = (String)in.readObject();
19           name = (String)in.readObject();
20           age = in.readInt();
21           scores = (List<Score>)in.readObject();
22       }

7、总结

  • Serializable是标识接口,没有需要实现的方法;Externalizable必须实现writeExternal和readExternal方法;

  • Serializable不需要有空构造,但Externalizable若含有参构造则必须有无参构造函数;

  • Serializable有两种实现方式,默认方式下自动序列化非static、transient关键字修饰的属性;

  • Serializable默认方式下使用反射实现序列化,性能稍弱,对属性顺序无要求;Externalizable要求属性的序列化与反序列化顺序一致,否则抛出 java.io.OptionalDataException 异常;

  • 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口;

  • 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化;

  • 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级、维护;

  • 序列化可以实现深拷贝;

8、预告:Redis中的序列化

原文地址:https://www.cnblogs.com/lijizhi/p/13300483.html