深入瞭解Java物件序列化
序列化字面上指的是安排在一個序列。它是一個過程Java在物件的狀態轉換為位元流。轉換維護一個序列按照提供的元資料,比如一個POJO。也許,這是由於這種轉變從抽象到一個原始序列的位元被稱為序列化的詞源。本文以序列化和其相關的概念,並試圖描繪它的一些角落和縫隙,及其實現的Java API。
概述
序列化使任何POJO可持久化轉換成位元組流。位元組流,然後可以儲存在一個檔案,記憶體或資料庫。
因此,序列化背後的關鍵思想是一個位元組流的概念。一個位元組流在Java是0和1的原子集合在一個預定義的序列。原子意味著他們沒有進一步的推導而來。原始位元非常靈活,可以轉化成任何東西:字元,數字,Java物件,等等。位獨立並不意味著什麼,除非他們生產和消耗的一些有意義的抽象的定義。在序列化,這意思是源自一個預定義的資料結構類和例項化都叫到一個活躍的實稱為Java物件。原始位元流然後儲存在一個儲存庫,如一個檔案在檔案系統中,陣列在記憶體的位元組數,或者儲存在資料庫中。在稍後的時間,這個位流可以恢復回原來的Java物件的逆過程。這個反向過程稱為反序列化。
圖2:序列化
物件序列化和反序列化過程設計遞迴地工作。這意味著,當任何物件序列化一個繼承層次結構的頂部,繼承的物件被序列化。引用物件位於遞迴和序列化。在恢復期間,反向過程應用和自底向上的方式是反序列化的物件。
序列化介面
序列化一個物件必須實現一個io。Serializable介面。這個介面不包含成員和用於指定一個類為可序列化的。如前所述,所有繼承子類也預設序列化。指定類的成員變數都堅持除了成員宣告為瞬態和靜態;他們不堅持。在下面的例子中,A類實現了Serializable。B類繼承類;也因此,B是可序列化的。B類包含一個引用類C . C類也必須實現Serializable介面;否則,io。NotSerializableException會在執行時丟擲。
包org.mano.example; 進口java.io.Serializable; 公共類實現了Serializable { 私有靜態最終長serialVersionUID l = 1; 公共字串; 公共靜態intSTATIC_VAL = 0; 公共瞬態int TRANSIENT_VAL = 0; / /……getter和setter } 包org.mano.example; 進口java.io.Serializable; 公共類C實現Serializable { 私有靜態最終長serialVersionUID l = 1; 私人c字串; / /……getter和setter } 包org.mano.example; 進口. io . *; 公共B類擴充套件了{ 私有靜態最終長serialVersionUID l = 1; 私人字串b; 私人C refC; / /……getter和setter 公共靜態void main(String[]args) 丟擲IOException,ClassNotFoundException { 字串filePath =“/ home /測試/ testfile.sz”; B B = new(); b。剛毛(“A”); b。setB(B類); b.getRefC()。國家經貿委(C類); b.setTRANSIENT_VAL(100); A.setSTATIC_VAL(400); B.setSTATIC_VAL(200); FileOutputStream fileOut = 新FileOutputStream(filePath); ObjectOutputStream objOut = 新ObjectOutputStream(fileOut); objOut.writeObject(b); objOut.flush(); fileOut.close(); FileInputStream fileIn = 新FileInputStream(filePath); ObjectInputStream objIn = 新ObjectInputStream(fileIn); b2 B =(B)objIn.readObject(); objIn.close(); fileIn.close(); system . out。println(" = " + b2.getA()); system . out。println(" B = " + b2.getB()); system . out。println(C = + .getC b2.getRefC()()); system . out。println(“靜態val = " + A.getSTATIC_VAL()); system . out。println(“靜態val = " + B.getSTATIC_VAL()); system . out。println(“瞬態val = " + b2.getTRANSIENT_VAL()); } }
輸出:
A=Class A
B=Class B
C=Class C
Static val=200
Static val=200
Transient val=0
如果您想使用一個物件從一個流中讀或寫,用readUnshared和writeUnshared方法代替readObject writeObject,分別。
觀察到的任何變化的靜態和瞬態變數不儲存在這個過程。有許多問題與序列化過程。正如我們所看到的,如果一個超類宣告可序列化的,所有的子也會序列化的類。這意味著,如果一個繼承B繼承了C繼承D…將序列化的物件!使這些類non-serializable領域的一個方法是使用瞬時修飾符。說,如果我們有50個欄位,我們不想堅持嗎?我們必須將這50欄位宣告為瞬態!在反序列化過程中可能出現類似的問題。如果我們想反序列化只有五個欄位而不是恢復所有10個欄位序列化之前和儲存?
有一個特定的方式停止序列化的繼承類。出路是編寫自己的readObject writeObject方法如下。
import java.io.*;
public class NonSerializedBook implements Serializable{
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException{
// ...
}
private void writeObject(ObjectOutputStream out)
throws IOException{
// ...
}
// ...
}
一個可序列化的類建議宣佈一個唯一的變數,稱為serialVersionUID,識別資料持久化。如果這個可選變數是不提供的,JVM建立一個由內部邏輯。這是浪費時間。
注意:JDK bin目錄包含一個serialver工具。這個工具可以用來serialVersionUID生成一個適當的值。儘管Java使用特定邏輯來生成這個數,它實際上是相當武斷的和可以是任何號碼。使用serialveras如下: import java.io.*; public class Book implements Serializable{ public static void main(String[] args){ System.out.println("Hello"); } } |
---|
編譯建立類檔案:
$ javac Book.java
$ serialver Book
的輸出將類似於圖3所示。
圖3:編譯後的類檔案的結果
簡而言之,一個序列化介面需要一些改變和更好地控制序列化和反序列化過程。
外部化介面提供了一些改進。但是,記住,序列化過程的自動實現Serializable介面在大多數情況下是好的。外部化是一個互補的介面,以減輕它的許多問題,更好的控制序列化/反序列化。
外部化介面
序列化和反序列化的過程很簡單,最錯綜複雜的儲存和恢復的物件都是自動處理的。有時,可能是程式設計師需要一些控制永續性過程;說,物件儲存需要壓縮或加密儲存之前,同樣的,解壓和解密需要在恢復過程中發生。這就是你需要實現外部化介面。外部化介面擴充套件了Serializable介面提供了兩個成員函式覆蓋的實現類。
- 空白readExternal(ObjectInput)
- 空白writeExternal(ObjectOutput)
readExternal方法讀取位元組流從ObjectInput writeStream寫道ObjectOutput。ObjectInput和ObjectOutput介面擴充套件DataInput DataOutput介面,分別。多型的讀寫方法被稱為序列化一個物件。
包org.mano.example;
進口. io . *;
進口java.util.Random;
公共類的書實現外部化{
私人int bookId;
私人isbn的字串;
私人字串標題;
私人字串出版商;
私人字串作者;
/ /……建構函式和getter和setter
@Override
公共空間writeExternal(ObjectOutput)
丟擲IOException {
out.writeInt(getBookId());
out.writeObject(getIsbn());
out.writeObject(getTitle());
out.writeObject(getPublisher());
out.writeObject(getAuthor());
}
@Override
公共空間readExternal(ObjectInput)
丟擲IOException,ClassNotFoundException {
setBookId(in.readInt());
.toString setIsbn(in.readObject()());
.toString setTitle(in.readObject()());
.toString setPublisher(in.readObject()());
.toString setAuthor(in.readObject()());
}
@Override
公共字串toString(){
返回“書(bookId = " + bookId +”,isbn = "
+ isbn +”,標題= " +標題+”,出版商= "
+出版商+”,作者= " +作者+“]”;
}
公共靜態最終字串FILE_PATH =
“/ home /測試/ mylib.sz”;
公共靜態void main(String[]args)
丟擲IOException,ClassNotFoundException {
隨機蘭德= new隨機();
書b1 =新書(rand.nextInt(100),
“123-456-789”,“圖論”、“φ”,“和”);
FileOutputStream fileOut =
新FileOutputStream(FILE_PATH);
ObjectOutputStream objOut =
新ObjectOutputStream(fileOut);
b1.writeExternal(objOut);
objOut.flush();
fileOut.close();
FileInputStream fileIn =
新FileInputStream(FILE_PATH);
ObjectInputStream objIn =
新ObjectInputStream(fileIn);
書b2 = new();
b2.readExternal(objIn);
objIn.close();
System.out.println(b2);
}
}
輸出:
Book [bookId=6, isbn=123-456-789, title=Graph Theory,
publisher=PHI, author=ND]
注意:任何欄位宣告為瞬態對外化沒有影響。(不像Serializable介面,儲存與外化。) |
---|
外化的序列化和反序列化過程更加靈活和給你更好的控制。但是,有幾點要記住當使用外部化介面:
- 實現外部化介面的類必須有一個預設的無引數建構函式。
- 你必須覆蓋並實現readExternal和writeExternal方法,明確。
- 每個序列化程式碼中定義在readExternal writeExternal方法和反序列化程式碼。
根據前面的屬性,任何非靜態內部類不是外部化。原因是JVM修改內部類的建構函式通過新增一個引用父類的編譯。因此,有一個無引數的建構函式的概念是不適用的非靜態內部類。因為我們可以控制領域堅持什麼,不借助readExternal和writeExternal方法,使與瞬態場non-persistable修飾符也是無關緊要的。
結論
序列化和外部化是一個標記介面來指定一個類的永續性。這些類的例項可能被轉換並存儲在儲存位元組流。儲存磁碟上的檔案或資料庫,甚至通過網路傳播。序列化過程和Java I / O流是分不開的。他們共同努力,把物件持久化的本質。