注:如果您發現任何不正確的內容,或者您想要分享有關本文主題的更多信息,請撰寫評論或聯繫我 [email protected]。
以下整理文章參考《java編程思想第四版》《EffectiveJava》oracle java官網 以及部分博客
以下環境基於 windows 10 java version “1.8.0_171”
文中代碼可能不太嚴謹,直供研究測試使用!
http://zjlgd.cn 請關注我的網站,大家一起進步!
是什麼
java的序列化是java I/O流內容的一部分。
序列化是將類的實例(類的對象)轉換爲字節流的過程。然後,此字節流可以作爲文件存儲在磁盤上,也可以通過網絡發送到另一臺計算機(使用另一臺JVM 實例化的對象)。
序列化對象時 僅會序列化 類的默認可序列化字段被定義爲非瞬態和非靜態字段。 也就是非
static
以及非transient
修飾字段
當程序關閉或者休眠時,使用 Serialization 將對象狀態保存在磁盤的某個文件上,當程序重新啓動時可以訪問該文件將對象重新new出來,這個過程可以稱之爲 反序列化
。(術語可能不太標準,僅供理解)
當一個類從序列化狀態恢復時,反序列化會繞過構造函數包括無參構造。
爲什麼
從何而來?
java的對象必須在內存在才能工作,一旦斷電或者java程序中斷,java產生的所有對象都煙消雲散。
那麼爲什麼不能跳過內存,直接使用硬盤來操作對象?
1. 按照 馮·諾依曼體系要求,數據必須在內存中,cpu才能操作。
2. 磁盤速度相比內存和cpu是非常慢的。
json不是一樣可以達到持久性的效果嗎,爲什麼要用java serializable?
將對象的值 轉爲 json類型格式存儲在文件,或者將對象值存儲與數據庫同樣可以達到 java 序列化,持久性的效果。java的序列化看你的選擇,功能就擺在那裏,用不用是你的事。用java自己寫的當然更加堅挺,持久!
但是在使萬物都成爲對象的精神中,如果能夠將一個對象聲明爲是“持久性”的,併爲我們處理掉所有細節,那將會顯得十分方便。
---- 摘自《java編程思想第四版》 18.12
- java序列話可以將對象轉化爲字節序列,且這個序列可以恢復爲原來的對象。在windows 實例化 的對象通過序列化將字節序列發給Unix系統,不必擔心數據在不同機器的不同表示,以及字節順序等等,java反序列化能夠準確的重新組裝。(支持RMI)
- 利用java的序列化可以實現輕量級的持久性
還有等等·····
怎麼樣
注意:
如果父類已實現Serializable接口,則子類不需要實現它,反之亦然。
僅通過序列化過程保存非靜態數據成員(not static)
靜態數據成員和臨時數據成員(transient)不通過序列化過程保存。因此,如果您不想保存非靜態數據成員的值,則將其設置爲瞬態。
反序列化對象時,永遠不會調用對象的構造函數。
關聯對象必須實現Serializable接口。
import java.io.Serializable;
public class Box implements Serializable {
private static final long serialVersionUID = 61485867982808238L;
private int width;
private int height;
public Box(int width, int height) {
this.width = width;
this.height = height;
}
public Box() {
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
// 爲了方便觀察結果,重寫toString方法
@Override
public String toString() {
return "Box{" +
"width=" + width +
", height=" + height +
'}';
}
}
- serialVersionUID 流的唯一標識符(序列版本) 適用於java序列化機制。簡單來說,JAVA序列化的機制是通過判斷類的serialVersionUID來驗證的版本一致的。
- 在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID於本地相應實體類的serialVersionUID進行比較。如果相同說明是一致的,可以進行反序列化,否則會出現反序列化版本一致的異常,即是InvalidCastException。
我可以不寫serialVersionUID嗎?
建議是 顯式聲明serialVersionUID。並且不爲 1L!
如果沒有顯式的聲明私有的,靜態的,最終的,long類型的serialVersionUID ,系統會自動根據這個類的相關數據,運行時生成標識。如果類的名稱或成員變量,實現繼承類有改變自動生成的標識也會改變。爲了避免這方面的問題,應該顯式聲明serialVersionUID,並且他應該是唯一的,而不是 1L。
取自 《Effective Java》第二版
關於 版本id 不同導致的詳細情況 請參考 https://blog.csdn.net/u014750606/article/details/80040130
A 序列化 B 反序列化
- 版本號不一致 —> 報錯 InvalidCastException
- 版本號一致,A 多字段 —> A多 字段丟失
- 版本號一致,B 少字段 —> A多 字段丟失.
- 版本號一致,B 多字段 —> B 多字段被賦予默認值
serializable序列化
serializable 接口是標記接口。這意味着如果您的類派生自此接口,則不必實現任何方法。這只是一個標記,Java運行時,在嘗試序列化類時,只會檢查類中是否存在此接口。如果類繼承層次結構中存在Serializable
接口,則Java運行時將負責類的序列化。 序列化我們需要執行以下幾個步驟
1. 創建一個文件的新 FileOutputStream,我們想要序列化該類到指定的文件
2. 創建 ObjectOutputStream 有參構造入參爲 第一步創建的FileOutputStream
3. 將對象寫入 ObjectOutputStream
4. 關閉所有流
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Test {
public static void SerializeToFile(Object classObject, String fileName) {
try {
// 這裏的FileOutputStream 只是爲了方便觀察,將序列化後的結構寫入文件。
FileOutputStream fileStream = new FileOutputStream(fileName);
// ObjectOutputStream繼承OutputStream,序列化基於字節因要使用InputStream/OutputStream繼承 // 層次結構
ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
objectStream.writeObject(classObject);
objectStream.close();
fileStream.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Box rect = new Box(18, 78);
// 我是windows 10 環境
SerializeToFile(rect, "G:\\rectSerialized");
}
}
執行下面這步將會將對象序列化
objectStream.writeObject(classObject);
在執行writeObject方法時,會校驗要序列化的類是否標記serializable,如果不是將會拋出 NotSerializableException。
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
對象的序列化會對對象內包含的引用進行跟蹤,並且保存他。
執行main函數之後,在我的G盤根目錄下生成了一個 rectSerialized 文件,大小爲1KB,我嘗試使用notepad++打開,但是顯示的內容看不懂。。。
注意:
序列化只是序列化對象的變量(可序列化的),並不涉及方法
serializable反序列化
反序列化即:將InputStream封裝在ObjectInputStream中,調用readObject(),得到的是一個向上轉型的 Object,需要向下轉型 強轉爲指定對象。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Test {
public static Object DeSerializeFromFileToObject(String fileName) {
try {
FileInputStream fileStream = new FileInputStream(new File(fileName));
ObjectInputStream objectStream = new ObjectInputStream(fileStream);
Object deserializeObject = objectStream.readObject();
objectStream.close();
fileStream.close();
return deserializeObject;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Box example =(Box)DeSerializeFromFileToObject("G:\\rectSerialized");
System.out.println(example.toString());
}
}
執行main函數結果如下:
Box{width=18, height=78}
Externalizable
序列化時會將本對象的某個子對象的某一部分進行序列化,這種情況可以實現Externalizable 接口。用法請自行參考。
誰用了Externalizable
哪個東西在哪裏使用了序列化
TODO
- 爲啥,數據必須在內存中,cpu才能操作。Why can’t CPUs get data directly from Hard drives?