欢迎来到农夫的博客

半亩方塘一鉴开, 天光云影共徘徊。 问渠哪得清如许? 为有源头活水来!

Java 对象序列化

 

 一、序列化和反序列化

对象的序列化:把对象转化为字节序列化的过程。
对象的反序列化:把字节序列化恢复为对象的过程。
对象序列化的用途
1、把对象永久的保存到硬盘上,通常存放在一个文件中。
2、在网络上传出对象的字节序列。
 
序列化的实现
        只有实现了SerializableExternalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。

二、序列化和反序列化示例

1、实现Serializable
Books.java
 1 package com.serialize.test;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * Created by Administrator on 2018/3/15.
 7  */
 8 public class Books implements Serializable {
 9     /**
10      * 序列化版本号
11      */
12     private static final long serialVersionUID = 1L;
13     private int bookId;
14     private String bookName;
15     private double bookPrice;
16     private String publish;
17     private transient int num;
18     @Override
19     public String toString() {
20         String str = "bookId-->" + bookId + "\nbookName-->"
21                 + bookName + "\nbookPrice-->" + bookPrice + "\npublish-->" + publish + "\nnum-->" + num;
22         return str;
23     }
24     public int getNum() {
25         return num;
26     }
27     public void setNum(int num) {
28         this.num = num;
29     }
30     public int getBookId() {
31         return bookId;
32     }
33     public void setBookId(int bookId) {
34         this.bookId = bookId;
35     }
36     public String getBookName() {
37         return bookName;
38     }
39     public void setBookName(String bookName) {
40         this.bookName = bookName;
41     }
42     public double getBookPrice() {
43         return bookPrice;
44     }
45     public void setBookPrice(double bookPrice) {
46         this.bookPrice = bookPrice;
47     }
48     public String getPublish() {
49         return publish;
50     }
51     public void setPublish(String publish) {
52         this.publish = publish;
53     }
54 }
View Code

 

SerializeTest.java
 1 package com.serialize.test;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * Created by Administrator on 2018/3/15.
 7  */
 8 public class SerializeTest {
 9     public static void main(String[] args) {
10         try {
11             serialize();
12             deserialize();
13         } catch (Exception e) {
14             e.printStackTrace();
15         }
16     }
17 
18     public static void serialize() throws Exception{
19         Books books = new Books();
20         books.setBookId(001);
21         books.setBookName("大型网站技术机构(核心原理与案例分析)");
22         books.setBookPrice(34.5);
23         books.setPublish("电子工业出版社");
24         books.setNum(123);
25         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\books.txt")));
26         oos.writeObject(books);
27         System.out.println("books对象序列化成功");
28         oos.close();
29     }
30 
31     public static void deserialize() throws Exception{
32         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\books.txt")));
33         Books books = (Books)ois.readObject();
34         System.out.println("books反对象序列化成功");
35         System.out.println(books.toString());
36         ois.close();
37     }
38 }
View Code
 
结果:
books对象序列化成功
books反对象序列化成功
bookId-->1
bookName-->大型网站技术机构(核心原理与案例分析)
bookPrice-->34.5
publish-->电子工业出版社
num-->0
 
transient 
从结果来看,transient 修改的num字段并没有序列化。Java中 利用transient关键字修饰,改字段对序列化不可见。
序列化Books成功后在E盘生成了一个books.txt文件,而反序列化Books是读取E盘的books.txt后生成了一个Books对象。
 
2、实现Externalizable
Books.java 修改为如下代码:
 1 package com.serialize.test;
 2 
 3 import java.io.*;
 4 
 5 /**
 6  * Created by Administrator on 2018/3/15.
 7  */
 8 public class Books implements Externalizable {
 9     /**
10      * 序列化版本号
11      */
12     private static final long serialVersionUID = 3L;
13     private int bookId;
14     private String bookName;
15     private double bookPrice;
16     private String publish;
17     private int num;
18 
19     public Books() {
20         System.out.println("Books 默认构造函数必须有");
21     }
22 
23     public Books(int bookId, String bookName, double bookPrice, String publish, int num) {
24         this.bookId = bookId;
25         this.bookName = bookName;
26         this.bookPrice = bookPrice;
27         this.publish = publish;
28         this.num = num;
29     }
30 
31     @Override
32     public String toString() {
33         String str = "bookId-->" + bookId + "\nbookName-->"
34                 + bookName + "\nbookPrice-->" + bookPrice + "\npublish-->" + publish + "\nnum-->" + num;
35         return str;
36     }
37     public int getNum() {
38         return num;
39     }
40     public void setNum(int num) {
41         this.num = num;
42     }
43     public int getBookId() {
44         return bookId;
45     }
46     public void setBookId(int bookId) {
47         this.bookId = bookId;
48     }
49     public String getBookName() {
50         return bookName;
51     }
52     public void setBookName(String bookName) {
53         this.bookName = bookName;
54     }
55     public double getBookPrice() {
56         return bookPrice;
57     }
58     public void setBookPrice(double bookPrice) {
59         this.bookPrice = bookPrice;
60     }
61     public String getPublish() {
62         return publish;
63     }
64     public void setPublish(String publish) {
65         this.publish = publish;
66     }
67 
68     @Override
69     public void writeExternal(ObjectOutput out) throws IOException {
70         out.writeInt(bookId);
71         out.writeObject(bookName);
72         out.writeDouble(bookPrice);
73         out.writeObject(publish);
74         out.writeInt(num);
75     }
76 
77     @Override
78     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
79         bookId = in.readInt();
80         bookName = (String)in.readObject();
81         bookPrice = in.readDouble();
82         publish = (String)in.readObject();
83         //num = in.readInt();
84     }
85 }
View Code
注意:writeExternal 和 readExternal 方法的应用;读取的变量必须已经序列化,且恢复调用的方法保持一致。如 out.writerObject("booksId")和in.readInt()是不对应的。
源码:
 * <p>The root object is completely restored when all of its fields and the
     * objects it references are completely restored.  At this point the object
     * validation callbacks are executed in order based on their registered
     * priorities. The callbacks are registered by objects (in the readObject
     * special methods) as they are individually restored
我理解的为: 对象的存储依据其的方法,属性的存储。反序列化对象的恢复根据序列化属性的优先级,其类型也必须对应存储类型。

三、版本号常量 serialVersionUID

        在SerializeTest例子中,如果没有指定Books类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。
        假如添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存    在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误,运行反序列化就会抛出异常:java.io.InvalidClassException。
 
抛异常: InvalidClassException

 
        意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。
        那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID
        在SerializeTest例子中,没有指定Books类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。
        所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存    在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。
serialVersionUID的取值

        serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的

        serialVersionUID的取值有可能也会发生变化。

        类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有

可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。

显式地定义serialVersionUID有两种用途:

1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

 

posted on 2018-03-20 00:13  allTime  阅读(365)  评论(0编辑  收藏  举报

导航