Java 对象序列化
一、序列化和反序列化
对象的序列化:把对象转化为字节序列化的过程。
对象的反序列化:把字节序列化恢复为对象的过程。
对象序列化的用途
1、把对象永久的保存到硬盘上,通常存放在一个文件中。
2、在网络上传出对象的字节序列。
序列化的实现
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。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 }
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 }
结果:
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 }
注意: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。
lift is made up of small pleasures.
生活是由各种微小的幸福构成。
日积月累,就会产生意想不到的Miracles。
每一天的坚持,每一天的收获,我与你同在!!