Java 對象序列化
java平臺允許在內存中創建可複用的java對象,但一般情況下,只有當JVM處於運行時,這些對象纔可能存在,即這些對象的生命生命週期不會比JVM生命週期長。但在現實應用中,就可能要求在JVM停止運行之後能夠保存(持久化)指定的對象,並在將來重新讀取被保存的對象。 Java對象序列化就能夠實現該功能。使用Java 對象序列化,在保存對象時,會將其轉換爲字節序列,這些序列可以保存在磁盤上,或通過網絡傳輸,以備以後重新恢復成原來的對象。
序列化緣由
有時候我們想把一個java對象寫入到磁盤文件或傳輸到網絡到其他計算機,這時我們就需要去把這個對象轉換成字節流,另一端接收字節流將其恢復成java對象。所以就有了java序列化的概念了。
好處
- 實現了數據的持久化
- 實現 遠程通信
序列化ID
序列化ID 提供兩種生成策略: 一是固定的1L,一個是隨機生成一個不重複的long類型數據(實際上是使用JDK工具生成) 。
Java 的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致,在進行反序列化時,JVM會把傳過來的字節流中的serialVersionUID與本地相應實體(類) 的serialVersionUID進行比較,如果相同,則進行反序列化,否則就會出現序列化版本不一致的異常。
如何實現序列化
- 實現Serializable 接口即可
當實現Serializable 接口的實體沒有顯示定義一個名爲serialVersionUID,類型爲long變量時,java序列化機制會根據編譯的class自動生成一個serialVersionUID做序列化版本比較,這種情況下,只有通一次編譯生成的class纔會生成相同的serialVersionUID。
實例:
普通對象序列化
public class UserInfo implements Serializable{
private static final long serialVersionUID = 1L;
private String userName;
private int age;
private String address;
public UserInfo(String userName, int age, String address) {
super();
System.out.println("有參數的構造器");
this.userName = userName;
this.age = age;
this.address = address;
}
..... // get、set 方法省略
}
/**
* ObjectStream
*
* @author mingx
*
*/
public class ObjectStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = null;
ObjectInputStream oin = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("E:/javaFile/object.txt"));
UserInfo userInfo = new UserInfo("KANNO爺", 25, "幽冥谷");
// 將對象寫入輸出流
oos.writeObject(userInfo);
// 反序列化操作
oin = new ObjectInputStream(new FileInputStream("E:/javaFile/object.txt"));
UserInfo user = (UserInfo) oin.readObject();
System.out.println("名字爲:" + user.getUserName() + "\n 年齡爲:" + user.getAge() + "\n地址爲:" + user.getAddress());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
oos.close();
}
if (oin != null) {
oin.close();
}
}
}
}
序列化: 上面的程序創建ObjectOutputStream,這個輸出流建立在一個文件輸出流的基礎上,使用writeObject() 方法將一個UserInfo對象寫入輸出流,運行上面程序生成一個object.txt文件,該文件的內容就是UserInfo對象。
反序列化:創建一個ObjectInputStream,這個輸入流是一個處理流,所以必須建立在其他節點流的基礎上,調用readObject() 方法讀取流中的對象,該方法返回一個Object類型的Java對象,如果程序知道該java對象的類型,將強制轉換成其真實類型。
注意:UserInfo類中只定義了一個有參數的構造函數,當我們反序列化讀取Java對象時,並沒有調用該構造函數,這表明反序列化機制無須通過構造器來初始化Java對象。
如果一個可序列化有多個父類,則該類的所有父類要麼是可序列化的,要麼有無參數的構造器,否則反序列化時會拋出InvalidClassException異常。
對象引用序列化
如果某個類的屬性類型是一個引用類型,那麼這個引用類型必須是可序列哈的,否則擁有該類型屬性的類是不可序列化的。例如:
public class ObjectReferenceSeriable {
public static void main(String[] args) throws IOException, ClassNotFoundException{
ObjectOutputStream out = null;
ObjectInputStream oin = null;
try {
//序列化操作
out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\teacher.txt"));
UserInfo userInfo = new UserInfo("龍幽", 25);
Teacher t1 = new Teacher("淨天教", userInfo);
Teacher t2 = new Teacher("蠻族", userInfo);
// 依次將四個對象寫入輸出流
out.writeObject(t1);
out.writeObject(t2);
out.writeObject(userInfo);
out.writeObject(t2);
//反序列化操作
oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\teacher.txt"));
// 依次讀取輸入流的四個對象
Teacher T1 = (Teacher) oin.readObject();
Teacher T2 = (Teacher) oin.readObject();
UserInfo user= (UserInfo) oin.readObject();
Teacher T3 = (Teacher) oin.readObject();
System.out.println("T1的student引用和user是否相同:" + (T1.getStudent() == user));
System.out.println("T2的student引用和user是否相同:" + (T2.getStudent() == user));
System.out.println("T2和T3是否同一個對象:" + (T2==T3));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally {
if(out !=null){
out.close();
}
if(oin !=null){
oin.close();
}
}
}
public static class Teacher implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private UserInfo student;
public Teacher(String name,UserInfo student){
this.name=name;
this.student= student;
}
...... //get、set省略
}
通過上面後面的程序代碼比較,可以看出T2和T3是同一個對象,T1的student引用的、T2的student引用的和user引用變量引用的是同一個對象。說明java序列化的機制如下圖:
從上圖中我們可以看出:當我們多次調用writeObject 輸出同一個對象時,程序只有第一次調用writeObject方法纔會將該對象轉換成字節序列並輸出。
但這會照成另外一個問題:當程序序列化一個可變對象時,程序只有第一次使用writeObject()方法輸出時纔會將該對象轉換成字節序列並輸出,即使後面該對象的屬性已改變,改變的屬性值也不會被輸出,例如:
public class SerializeMutable {
public static void main(String[] args) throws IOException{
ObjectOutputStream out = null;
ObjectInputStream oin = null;
try {
out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\mutable.txt"));
UserInfo user = new UserInfo("pepper",25);
out.writeObject(user);
user.setUserName("Tony");
out.writeObject(user);
// 反序列化
oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\mutable.txt"));
UserInfo u1 = (UserInfo) oin.readObject();
UserInfo u2 = (UserInfo) oin.readObject();
System.out.println(u1 == u2);
System.out.println(u2.getUserName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
finally {
if(out !=null){
out.close();
}
if(oin !=null){
oin.close();
}
}
}
}
運行上面程序,程序比較讀取的Java對象完全相同,第二次讀取的UserInfo對象的userName 屬性依然是pepper,說明被改變的userInfo對象並沒有被寫入。
**靜態變量序列化 **
public class StaticSeriable implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
//定義初始值爲5
public static int staitcAcitivar = 5;
public static void main(String[] args){
ObjectOutputStream out;
try {
out = new ObjectOutputStream(new FileOutputStream("result.obj"));
out.writeObject(new StaticSeriable());
out.close();
//序列化後修改爲10
StaticSeriable.staitcAcitivar = 10;
ObjectInputStream oIn = new ObjectInputStream(new FileInputStream("result.obj"));
StaticSeriable t = (StaticSeriable) oIn.readObject();
oIn.close();
//再讀取,通過t.staticActivar打印新值
System.out.println(t.staitcAcitivar);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上面程序將對象序列化後,修改靜態便令的值,再將對象從序列化對象讀取出來,打印對象的靜態變量值,是10而不是5,這是爲什麼呢?這是因爲序列化時,並不保存靜態變量,而是保存對象的狀態,靜態變量屬於類的狀態。