1. 程式人生 > >java序列化與反序列化系列問題

java序列化與反序列化系列問題

很多商業專案用到資料庫、記憶體對映檔案和普通檔案來完成專案中的序列化處理的需求,但是這些方法很少會依靠於Java序列化。本文也不是用來解釋序列化的,而是一起來看看面試中有關序列化的問題,這些問題你很有可能不瞭解。“Java序列化指的是將物件轉換程位元組格式並將物件狀態儲存在檔案中,通常是.ser副檔名的檔案。然後可以通過.ser檔案重新建立Java物件,這個過程為返序列化”

Java序列化的API中提供了開發人員進行序列化物件的機制,通過Serializable和Externalizable介面。

一起看看這些問題:
1)Java中的Serializable介面和Externalizable介面有什麼區別?

這個是面試中關於Java序列化問的最多的問題。我的回答是,Externalizable介面提供了兩個方法writeExternal()和readExternal()。這兩個方法給我們提供了靈活處理Java序列化的方法,通過實現這個介面中的兩個方法進行物件序列化可以替代Java中預設的序列化方法。正確的實現Externalizable介面可以大幅度的提高應用程式的效能。

2)Serializable介面中有借個方法?如果沒有方法的話,那麼這麼設計Serializable介面的目的是什麼?

Serializable介面在java.lang包中,是Java序列化機制的核心組成部分。它裡面沒有包含任何方法,我們稱這樣的介面為標識介面。如果你的類實現了Serializable介面,這意味著你的類被打上了“可以進行序列化”的標籤,並且也給了編譯器指示,可以使用序列化機制對這個物件進行序列化處理。

3)什麼是serialVersionUID?如果你沒有定義serialVersionUID意味著什麼?

SerialVersionUID應該是你的類中的一個publicstatic final型別的常量,如果你的類中沒有定義的話,那麼編譯器將丟擲警告。如果你的類中沒有制定serialVersionUID,那麼Java編譯器會根據類的成員變數和一定的演算法生成用來表達物件的serialVersionUID ,通常是用來表示類的雜湊值(hash code)。結論是,如果你的類沒有實現SerialVersionUID,那麼如果你的類中如果加入或者改變成員變數,那麼已經序列化的物件將無法反序列化。這是以為,類的成員變數的改變意味這編譯器生成的SerialVersionUID的值不同。Java序列化過程是通過正確SerialVersionUID來對已經序列化的物件進行狀態恢復。

4)當物件進行序列化的時候,如果你不希望你的成員變數進行序列化,你怎麼辦?

這個問題也會這麼問,如何使用暫態型別的成員變數?暫態和靜態成員變數是否會被序列化等等。如果你不希望你的物件中的成員變數的狀態得以儲存,你可以根據需求選擇transient或者static型別的變數,這樣的變數不參與Java序列化處理的過程。

5)如果一個類中的成員變數是其它符合型別的Java類,而這個類沒有實現Serializable介面,那麼當物件序列化的時候會怎樣?

如果你的一個物件進行序列化,而這個物件中包含另外一個引用型別的成員程式設計,而這個引用的類沒有實現Serializable介面,那麼當物件進行序列化的時候會丟擲“NotSerializableException“的執行時異常。

6)如果一個類是可序列化的,而他的超類沒有,那麼當進行反序列化的時候,那些從超類繼承的例項變數的值是什麼?

Java中的序列化處理例項變數只會在所有實現了Serializable介面的繼承支路上展開。所以當一個類進行反序列化處理的時候,超類沒有實現Serializable介面,那麼從超類繼承的例項變數會通過為實現序列化介面的超類的建構函式進行初始化。

7)Can you Customize Serialization process or can you override defaultSerialization process in Java?

7)你能夠自定義序列化處理的程式碼嗎或者你能過載Java中預設的序列化方法嗎?

答案是肯定的,可以。我們都知道可以通過ObjectOutputStream中的writeObject()方法寫入序列化物件,通過ObjectInputStream中的readObject()讀入反序列化的物件。這些都是Java虛擬機器提供給你的兩個方法。如果你在你的類中定義了這兩個方法,那麼JVM就會用你的方法代替原有預設的序列化機制的方法。你可以通過這樣的方式類自定義序列化和反序列化的行為。需要注意的一點是,最好將這兩個方法定義為private,以防止他們被繼承、重寫和過載。也只有JVM可以訪問到你的類中所有的私有方法,你不用擔心方法私有不會被呼叫到,Java序列化過程會正常工作。

8)假設一個新的類的超類實現了Serializable介面,那麼如何讓這個新的子類不被序列化?

如果一個超類已經序列化了,那麼無法通過是否實現什麼介面的方式再避免序列化的過程了,但是也還有一種方式可以使用。那就是需要你在你的類中重新實現writeObject()和readObject()方法,並在方法實現中通過丟擲NotSerializableException。

9)在Java進行序列化和反序列化處理的時候,哪些方法被使用了?

這個是面試中常見的問題,主要用來考察你是否對readObject()、writeObject()、readExternal()和writeExternal()方法的使用熟悉。Java序列化是通過java.io.ObjectOutputStream這個類來完成的。這個類是一個過濾器流,這個類完成對底層位元組流的包裝來進行序列化處理。我們通過ObjectOutputStream.writeObject(obj)進行序列化,通過ObjectInputStream.readObject()進行反序列化。對writeObject()方法的呼叫會觸發Java中的序列化機制。readObject()方法用來將已經持久化的位元組資料反向建立Java物件,該方法返回Object型別,需要強制轉換成你需要的正確型別。

10)Suppose you have a class which you serialized it and stored in persistence andlater modified that class to add a new field. What will happen if youdeserialize the object already serialized?

10)假設你有一個類並且已經將這個類的某一個物件序列化儲存了,那麼如果你在這個類中加入了新的成員變數,那麼在反序列化剛才那個已經存在的物件的時候會怎麼樣?

這個取決於這個類是否有serialVersionUID成員。通過上面的,我們已經知道如果你的類沒有提供serialVersionUID,那麼編譯器會自動生成,而這個serialVersionUID就是物件的hash code值。那麼如果加入新的成員變數,重新生成的serialVersionUID將和之前的不同,那麼在進行反序列化的時候就會產生java.io.InvalidClassException的異常。這就是為什麼要建議為你的程式碼加入serialVersionUID的原因所在了。

11)JAVA反序列化時會將NULL值變成""字元!!

在java中socket傳輸資料時,資料型別往往比較難選擇。可能要考慮頻寬、跨語言、版本的相容等問題。比較常見的做法有兩種:一是把物件包裝成JSON字串傳輸,二是採用java物件的序列化和反序列化。隨著Google工具protoBuf的開源,protobuf也是個不錯的選擇。對JSON,Object Serialize,ProtoBuf 做個對比。

定義一個待傳輸的物件UserVo:

Java程式碼  

1. public class UserVo{  

2.     private String name;  

3.     private int age;  

4.     private long phone;  

5.       

6.     private List<UserVo> friends;  

7. ……  

8. }  

 初始化UserVo的例項src:

Java程式碼  

1. UserVo src = new UserVo();  

2. src.setName("Yaoming");  

3. src.setAge(30);  

4. src.setPhone(13789878978L);  

5.       

6. UserVo f1 = new UserVo();  

7. f1.setName("tmac");  

8. f1.setAge(32);  

9. f1.setPhone(138999898989L);  

10.UserVo f2 = new UserVo();  

11.f2.setName("liuwei");  

12.f2.setAge(29);  

13.f2.setPhone(138999899989L);  

14.          

15.List<UserVo> friends = new ArrayList<UserVo>();  

16.friends.add(f1);  

17.friends.add(f2);  

18.src.setFriends(friends);  

JSON格式

採用Google的gson-2.2.2.jar進行轉義

Java程式碼  

1. Gson gson = new Gson();  

2. String json = gson.toJson(src);  

 得到的字串:

Js程式碼  

1. {"name":"Yaoming","age":30,"phone":13789878978,"friends":[{"name":"tmac","age":32,"phone":138999898989},{"name":"liuwei","age":29,"phone":138999899989}]}  

位元組數為153

Json的優點:明文結構一目瞭然,可以跨語言,屬性的增加減少對解析端影響較小。缺點:位元組數過多,依賴於不同的第三方類庫。

Object Serialize

UserVo實現Serializalbe介面,提供唯一的版本號:

Java程式碼  

1. public class UserVo implements Serializable{  

2.   

3.     private static final long serialVersionUID = -5726374138698742258L;  

4.     private String name;  

5.     private int age;  

6.     private long phone;  

7.       

8.     private List<UserVo> friends;  

序列化方法:

Java程式碼  

1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  

2. ObjectOutputStream os = new ObjectOutputStream(bos);  

3. os.writeObject(src);  

4. os.flush();  

5. os.close();  

6. byte[] b = bos.toByteArray();  

7. bos.close();  

位元組數是238

反序列化:

Java程式碼  

1. ObjectInputStream ois = new ObjectInputStream(fis);  

2. vo = (UserVo) ois.readObject();  

3. ois.close();  

4. fis.close();  

Object Serializalbe 優點:java原生支援,不需要提供第三方的類庫,使用比較簡單。缺點:無法跨語言,位元組數佔用比較大,某些情況下對於物件屬性的變化比較敏感。 

物件在進行序列化和反序列化的時候,必須實現Serializable介面,但並不強制宣告唯一的serialVersionUID

是否宣告serialVersionUID對於物件序列化的向上向下的相容性有很大的影響。我們來做個測試:

思路一

把UserVo中的serialVersionUID去掉,序列化儲存。反序列化的時候,增加或減少個欄位,看是否成功。

Java程式碼  

1. public class UserVo implements Serializable{  

2.     private String name;  

3.     private int age;  

4.     private long phone;  

5.       

6.     private List<UserVo> friends;  

儲存到檔案中:

Java程式碼  

1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  

2. ObjectOutputStream os = new ObjectOutputStream(bos);  

3. os.writeObject(src);  

4. os.flush();  

5. os.close();  

6. byte[] b = bos.toByteArray();  

7. bos.close();  

8.   

9. FileOutputStream fos = new FileOutputStream(dataFile);  

10.fos.write(b);  

11.fos.close();  

增加或者減少欄位後,從檔案中讀出來,反序列化:

Java程式碼  

1. FileInputStream fis = new FileInputStream(dataFile);  

2. ObjectInputStream ois = new ObjectInputStream(fis);  

3. vo = (UserVo) ois.readObject();  

4. ois.close();  

5. fis.close();  

結果:丟擲異常資訊

Java程式碼  

1. Exception in thread "main" java.io.InvalidClassException: serialize.obj.UserVo; local class incompatible: stream classdesc serialVersionUID = 3305402508581390189, local class serialVersionUID = 7174371419787432394  

2.     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)  

3.     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)  

4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)  

5.     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)  

6.     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)  

7.     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)  

8.     at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)  

9.     at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)  

思路二

eclipse指定生成一個serialVersionUID,序列化儲存,修改欄位後反序列化

略去程式碼

結果:反序列化成功

結論

如果沒有明確指定serialVersionUID,序列化的時候會根據欄位和特定的演算法生成一個serialVersionUID,當屬性有變化時這個id發生了變化,所以反序列化的時候就會失敗。丟擲“本地classd的唯一id和流中class的唯一id不匹配”。

jdk文件關於serialVersionUID的描述:

寫道

如果可序列化類未顯式宣告 serialVersionUID,則序列化執行時將基於該類的各個方面計算該類的預設 serialVersionUID 值,如“Java(TM)物件序列化規範”中所述。不過,強烈建議所有可序列化類都顯式宣告serialVersionUID ,原因是計算預設的 serialVersionUID 對類的詳細資訊具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。因此,為保證serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類必須宣告一個明確的 serialVersionUID 值。還強烈建議使用private 修飾符顯示宣告 serialVersionUID(如果可能),原因是這種宣告僅應用於直接宣告類 -- serialVersionUID 欄位作為繼承成員沒有用處。陣列類不能宣告一個明確的 serialVersionUID,因此它們總是具有預設的計算值,但是陣列類沒有匹配 serialVersionUID 值的要求。

Google ProtoBuf

protocol buffers 是google內部得一種傳輸協議,目前專案已經開源(http://code.google.com/p/protobuf/)。它定義了一種緊湊得可擴充套件得二進位制協議格式,適合網路傳輸,並且針對多個語言有不同得版本可供選擇。

以protobuf-2.5.0rc1為例,準備工作:

下載原始碼,解壓,編譯,安裝

Shell程式碼  

1. tar zxvf protobuf-2.5.0rc1.tar.gz  

2. ./configure  

3. ./make  

4. ./make install  

 測試:

Shell程式碼  

1. MacBook-Air:~ ming$ protoc --version  

2. libprotoc 2.5.0  

 安裝成功!進入原始碼得java目錄,用mvn工具編譯生成所需得jar包,protobuf-java-2.5.0rc1.jar

1、編寫.proto檔案,命名UserVo.proto 

Text程式碼  

1. package serialize;  

2.   

3. option java_package = "serialize";  

4. option java_outer_classname="UserVoProtos";  

5.   

6. message UserVo{  

7.     optional string name = 1;  

8.     optional int32 age = 2;  

9.     optional int64 phone = 3;  

10.    repeated serialize.UserVo friends = 4;  

11.}  

2、在命令列利用protoc 工具生成builder類

Shell程式碼  

1. protoc -IPATH=.proto檔案所在得目錄 --java_out=java檔案的輸出路徑  .proto的名稱   

 得到UserVoProtos類

3、編寫序列化程式碼

Java程式碼  

1. UserVoProtos.UserVo.Builder builder = UserVoProtos.UserVo.newBuilder();  

2. builder.setName("Yaoming");  

3. builder.setAge(30);  

4. builder.setPhone(13789878978L);  

5.           

6. UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder();  

7. builder1.setName("tmac");  

8. builder1.setAge(32);  

9. builder1.setPhone(138999898989L);  

10.          

11.UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder();  

12.builder2.setName("liuwei");  

13.builder2.setAge(29);  

14.builder2.setPhone(138999899989L);  

15.          

16.builder.addFriends(builder1);  

17.builder.addFriends(builder2);  

18.          

19.UserVoProtos.UserVo vo = builder.build();  

20.          

21.byte[] v = vo.toByteArray();  

位元組數53

4、反序列化

Java程式碼  

1. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  

2. System.out.println(uvo.getFriends(0).getName());  

 結果:tmac,反序列化成功

google protobuf 優點:位元組數很小,適合網路傳輸節省io,跨語言 。缺點:需要依賴於工具生成程式碼。

工作機制

proto檔案是對資料的一個描述,包括欄位名稱,型別,位元組中的位置。protoc工具讀取proto檔案生成對應builder程式碼的類庫。protoc xxxxx  --java_out=xxxxxx 生成java類庫。builder類根據自己的演算法把資料序列化成位元組流,或者把位元組流根據反射的原理反序列化成物件。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。

proto檔案中的欄位型別和java中的對應關係:

詳見:https://developers.google.com/protocol-buffers/docs/proto

 .proto Type

 java Type

 c++ Type

double

 double

 double

float

 float

 float

int32

 int

 int32

int64

 long 

 int64

uint32

 int

 uint32

unint64

 long

 uint64

sint32

 int

 int32

sint64

 long

 int64

fixed32

 int

 uint32

fixed64

 long

 uint64

sfixed32

 int

 int32

sfixed64

 long

 int64

bool

 boolean

 bool

string

 String

 string

bytes

 byte

 string

欄位屬性的描述:

寫道

required: a well-formed message must haveexactly one of this field.
optional: a well-formed message can have zero or one of this field (but notmore than one).
repeated: this field can be repeated any number of times (including zero) in awell-formed message. The order of the repeated values will be preserved.

protobuf 在序列化和反序列化的時候,是依賴於.proto檔案生成的builder類完成,欄位的變化如果不表現在.proto檔案中就不會影響反序列化,比較適合欄位變化的情況。做個測試:

把UserVo序列化到檔案中:

Java程式碼  

1. UserVoProtos.UserVo vo = builder.build();  

2. byte[] v = vo.toByteArray();  

3. FileOutputStream fos = new FileOutputStream(dataFile);  

4. fos.write(vo.toByteArray());  

5. fos.close();  

為UserVo增加欄位,對應的.proto檔案:

Text程式碼  

1. package serialize;  

2.   

3. option java_package = "serialize";  

4. option java_outer_classname="UserVoProtos";  

5.   

6. message UserVo{  

7.     optional string name = 1;  

8.     optional int32 age = 2;  

9.     optional int64 phone = 3;  

10.    repeated serialize.UserVo friends = 4;  

11.    optional string address = 5;  

12.}  

從檔案中反序列化回來:

Java程式碼  

1. FileInputStream fis = new FileInputStream(dataFile);  

2. byte[] dstb = new byte[fis.available()];  

3. for(int i=0;i<dstb.length;i++){  

4.     dstb[i] = (byte)fis.read();  

5. }  

6. fis.close();  

7. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  

8. System.out.println(uvo.getFriends(0).getName());  

 成功得到結果。

三種方式對比傳輸同樣的資料,google protobuf只有53個位元組是最少的。結論:

方式

優點

缺點

JSON

跨語言、格式清晰一目瞭然

位元組數比較大,需要第三方類庫

Object Serialize

java原生方法不依賴外部類庫

位元組數比較大,不能跨語言

Google protobuf

跨語言、位元組數比較少

編寫.proto配置用protoc工具生成對應的程式碼

序列化是指將一個物件序列化成位元組流,便於儲存或者網路傳輸;而反序列化恰好相反,將位元組流,變回一個物件.我們平常用的比較多的是hessian序列化方式和java序列化方式,兩種序列化方式的效率,以及序列化大小是不一樣的,從測試結果看,hessian好一點.下面寫了一個hessian序列化示例(沒有檔案IO與網路IO,純粹序列化與反序列化):

/**

 * hessian序列化

 * @param object

 * @return

 * @throws Exception

 */

public static byte[] serialize(Object object) throws Exception{

if(object==null){

throw new NullPointerException();

}

ByteArrayOutputStream os = new ByteArrayOutputStream();

HessianSerializerOutput hessianOutput=new HessianSerializerOutput(os);

hessianOutput.writeObject(object);

return os.toByteArray();

}

/**

 * hessian反序列化

 * @param bytes

 * @return

 * @throws Exception

 */

public static Object deserialize(byte[] bytes) throws Exception{

if(bytes==null){

throw new NullPointerException();

}

ByteArrayInputStream is = new ByteArrayInputStream(bytes);

HessianSerializerInput hessianInput=new HessianSerializerInput(is);

Object object = hessianInput.readObject();

return object;

}

另外一種常見的是java序列化方式:

/**

 * java序列化

 * @param obj

 * @return

 * @throws Exception

 */

public static byte[] serialize(Object obj) throws Exception {

if (obj == null)

throw new NullPointerException();

ByteArrayOutputStream os = new ByteArrayOutputStream();

ObjectOutputStream out = new ObjectOutputStream(os);

out.writeObject(obj);

return os.toByteArray();

}

/**

 * java反序列化

 * @param by

 * @return

 * @throws Exception

 */

public static Object deserialize(byte[] by) throws Exception {

if (by == null)

throw new NullPointerException();

ByteArrayInputStream is = new ByteArrayInputStream(by);

ObjectInputStream in = new ObjectInputStream(is);

return in.readObject();

}

 當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個Java物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java物件。

把Java物件轉換為位元組序列的過程稱為物件的序列化

把位元組序列恢復為Java物件的過程稱為物件的反序列化

物件的序列化主要有兩種用途:

1) 把物件的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中;

2) 在網路上傳送物件的位元組序列。

一.             JDK類庫中的序列化API

java.io.ObjectOutputStream代表物件輸出流,它的writeObject(Objectobj)方法可對引數指定的obj物件進行序列化,把得到的位元組序列寫到一個目標輸出流中。

java.io.ObjectInputStream代表物件輸入流,它的readObject()方法從一個源輸入流中讀取位元組序列,再把它們反序列化為一個物件,並將其返回。、

只有實現了Serializable和Externalizable介面的類的物件才能被序列化。Externalizable介面繼承自Serializable介面,實現Externalizable介面的類完全由自身來控制序列化的行為,而僅實現Serializable介面的類可以採用預設的序列化方式 。

物件序列化包括如下步驟:

1) 建立一個物件輸出流,它可以包裝一個其他型別的目標輸出流,如檔案輸出流;

2) 通過物件輸出流的writeObject()方法寫物件。

物件反序列化的步驟如下:

1) 建立一個物件輸入流,它可以包裝一個其他型別的源輸入流,如檔案輸入流;

2) 通過物件輸入流的readObject()方法讀取物件。

下面讓我們來看一個對應的例子,類的內容如下:

import java.io.*;

import java.util.Date;

/**

 * 物件的序列化和反序列化測試類.    

 * @author <ahref="mailto:[email protected]">AmigoXie</a>

 * @version 1.0

 * Creation date: 2007-9-15- 下午21:45:48

 */

public class ObjectSaver {

      /**

       * @param args

       * @author <ahref="mailto:[email protected]">AmigoXie</a>

       * Creation date: 2007-9-15 - 下午21:45:37

       */

      public static void main(String[] args) throws Exception {

             ObjectOutputStream out = new ObjectOutputStream

                    (new FileOutputStream("D:""objectFile.obj"));

             //序列化物件

             Customer customer = new Customer("阿蜜果",24);

             out.writeObject("你好!");

             out.writeObject(new Date());

             out.writeObject(customer);

             out.writeInt(123); //寫入基本型別資料

             out.close();

             //反序列化物件

             ObjectInputStream in = new ObjectInputStream

                    (new FileInputStream("D:""objectFile.obj"));

             System.out.println("obj1=" + (String) in.readObject());

             System.out.println("obj2=" + (Date) in.readObject());

             Customer obj3 = (Customer) in.readObject();

             System.out.println("obj3=" + obj3);

             int obj4 = in.readInt();

             System.out.println("obj4=" + obj4);

             in.close();

      }

}

class Customer implementsSerializable {

      private String name;

      private int age;

      public Customer(String name, int age) {

             this.name = name;

             this.age = age;

      }

      public String toString() {

             return "name=" + name + ", age=" + age;

      }

}

       輸出結果如下:

obj1=你好!

obj2=Sat Sep 15 22:02:21 CST 2007

obj3=name=阿蜜果,age=24

obj4=123

    因此例比較簡單,在此不再詳述。

二.實現Serializable介面

ObjectOutputStream只能對Serializable介面的類的物件進行序列化。預設情況下,ObjectOutputStream按照預設方式序列化,這種序列化方式僅僅對物件的非transient的例項變數進行序列化,而不會序列化物件的transient的例項變數,也不會序列化靜態變數。

當ObjectOutputStream按照預設方式反序列化時,具有如下特點:

1)              如果在記憶體中物件所屬的類還沒有被載入,那麼會先載入並初始化這個類。如果在classpath中不存在相應的類檔案,那麼會丟擲ClassNotFoundException;

2)              在反序列化時不會呼叫類的任何構造方法。

如果使用者希望控制類的序列化方式,可以在可序列化類中提供以下形式的writeObject()和readObject()方法。

privatevoid writeObject(java.io.ObjectOutputStream out) throws IOException

privatevoid readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException;

當ObjectOutputStream對一個Customer物件進行序列化時,如果該物件具有writeObject()方法,那麼就會執行這一方法,否則就按預設方式序列化。在該物件的writeObjectt()方法中,可以先呼叫ObjectOutputStream的defaultWriteObject()方法,使得物件輸出流先執行預設的序列化操作。同理可得出反序列化的情況,不過這次是defaultReadObject()方法。

有些物件中包含一些敏感資訊,這些資訊不宜對外公開。如果按照預設方式對它們序列化,那麼它們的序列化資料在網路上傳輸時,可能會被不法份子竊取。對於這類資訊,可以對它們進行加密後再序列化,在反序列化時則需要解密,再恢復為原來的資訊。

預設的序列化方式會序列化整個物件圖,這需要遞迴遍歷物件圖。如果物件圖很複雜,遞迴遍歷操作需要消耗很多的空間和時間,它的內部資料結構為雙向列表。

在應用時,如果對某些成員變數都改為transient型別,將節省空間和時間,提高序列化的效能。

三.             實現Externalizable介面

Externalizable介面繼承自Serializable介面,如果一個類實現了Externalizable介面,那麼將完全由這個類控制自身的序列化行為。Externalizable介面聲明瞭兩個方法:

publicvoid writeExternal(ObjectOutput out) throws IOException

publicvoid readExternal(ObjectInput in) throws IOException , ClassNotFoundException

前者負責序列化操作,後者負責反序列化操作。

在對實現了Externalizable介面的類的物件進行反序列化時,會先呼叫類的不帶引數的構造方法,這是有別於預設反序列方式的。如果把類的不帶引數的構造方法刪除,或者把該構造方法的訪問許可權設定為private、預設或protected級別,會丟擲java.io.InvalidException:no valid constructor異常。

四.             可序列化類的不同版本的序列化相容性

凡是實現Serializable介面的類都有一個表示序列化版本識別符號的靜態變數:

private static final longserialVersionUID;

以上serialVersionUID的取值是Java執行時環境根據類的內部細節自動生成的。如果對類的原始碼作了修改,再重新編譯,新生成的類檔案的serialVersionUID的取值有可能也會發生變化。

類的serialVersionUID的預設值完全依賴於Java編譯器的實現,對於同一個類,用不同的Java編譯器編譯,有可能會導致不同的serialVersionUID,也有可能相同。為了提高哦啊serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,為它賦予明確的值。顯式地定義serialVersionUID有兩種用途:

1)              在某些場合,希望類的不同版本對序列化相容,因此需要確保類的不同版本具有相同的serialVersionUID;

2)              在某些場合,不希望類的不同版本對序列化相容,因此需要確保類的不同版本具有不同的serialVersionUID。

Java序列化演算法透析

  Serialization(序列化)是一種將物件以一連串的位元組描述的過程;反序列化deserialization是一種將這些位元組重建成一個物件的過程。Java序列化API提供一種處理物件序列化的標準機制。在這裡你能學到如何序列化一個物件,什麼時候需要序列化以及Java序列化的演算法,我們用一個例項來示範序列化以後的位元組是如何描述一個物件的資訊的。

序列化的必要性

Java中,一切都是物件,在分散式環境中經常需要將Object從這一端網路或裝置傳遞到另一端。這就需要有一種可以在兩端傳輸資料的協議。Java序列化機制就是為了解決這個問題而產生。

如何序列化一個物件

一個物件能夠序列化的前提是實現Serializable介面,Serializable介面沒有方法,更像是個標記。有了這個標記的Class就能被序列化機制處理。

import java.io.Serializable;  
class TestSerial implements Serializable {         
    public byte version = 100;        
    public byte count = 0;  
}

然後我們寫個程式將物件序列化並輸出。ObjectOutputStream能把Object輸出成Byte流。我們將Byte流暫時儲存到temp.out檔案裡。

public static void main(String args[]) throwsIOException {         
    FileOutputStream fos = newFileOutputStream("temp.out");        
    ObjectOutputStream oos = newObjectOutputStream(fos);         
    TestSerial ts = new TestSerial();         
    oos.writeObject(ts);         
    oos.flush();         
    oos.close();
}

如果要從持久的檔案中讀取Bytes重建物件,我們可以使用ObjectInputStream。 

public staticvoid main(String args[]) throws IOException {    
    FileInputStream fis = newFileInputStream("temp.out");         
    ObjectInputStream oin = newObjectInputStream(fis);          
    TestSerial ts = (TestSerial) oin.readObject();          
    System.out.println("version="+ts.version);   
}

執行結果為 100.

物件的序列化格式

將一個物件序列化後是什麼樣子呢?開啟剛才我們將物件序列化輸出的temp.out檔案

以16進位制方式顯示。內容應該如下:

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65

73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05

63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78

70 00 64

這一坨位元組就是用來描述序列化以後的TestSerial物件的,我們注意到TestSerial類中只有兩個域:

public byte version = 100;

public byte count = 0;

且都是byte型,理論上儲存這兩個域只需要2個byte,但是實際上temp.out佔據空間為51bytes,也就是說除了資料以外,還包括了對序列化物件的其他描述

Java的序列化演算法

序列化演算法一般會按步驟做如下事情:

將物件例項相關的類元資料輸出。

遞迴地輸出類的超類描述直到不再有超類。

類元資料完了以後,開始從最頂層的超類開始輸出物件例項的實際資料值。

從上至下遞迴輸出例項的資料

我們用另一個更完整覆蓋所有可能出現的情況的例子來說明:

class parentimplements Serializable {         
    int parentVersion = 10;  
}

class containimplements Serializable{         
    Int containVersion = 11;  

public classSerialTest extends parent implements Serializable {  
       int version = 66;  
       contain con = new contain();  
       public int getVersion()
              return version;
  
       } 
       public static void main(Stringargs[]) throws IOException { 
              FileOutputStream fos = newFileOutputStream("temp.out"); 
              ObjectOutputStream oos =new ObjectOutputStream(fos); 
              SerialTest st = newSerialTest(); 
              oos.writeObject(st); 
              oos.flush(); 
              oos.close(); 
       } 

相關推薦

java序列序列系列問題

很多商業專案用到資料庫、記憶體對映檔案和普通檔案來完成專案中的序列化處理的需求,但是這些方法很少會依靠於Java序列化。本文也不是用來解釋序列化的,而是一起來看看面試中有關序列化的問題,這些問題你很有可能不瞭解。“Java序列化指的是將物件轉換程位元組格式並將物件狀態儲存在

Java核心類庫-IO-對象流(實現序列序列

.get throws 反序 code row cts new java cep 使用對象流來完成序列化和反序列化操作:   ObjectOutputStream:通過writeObject()方法做序列化操作的   ObjectInputStream:通過readObje

Java IO-5 序列序列

str ride log getname file urn turn objects transient 建一個Person類 1 package demo05; 2 3 import java.io.Serializable; 4 5 public cla

Java序列序列

setname [] 進制 方式 gets 創建 保存 ati 取數據 Java序列化與反序列化是什麽?為什麽需要序列化與反序列化?如何實現Java序列化與反序列化?本文圍繞這些問題進行了探討。 1.Java序列化與反序列化 Java序列化是指把Java對象轉換為字節序

Java將對象寫入文件讀出——序列序列

ansi print 成員 trace 對象的引用 ack lose 靜態 spa Java類中對象的序列化工作是通過ObjectOutputStream和ObjectInputStream來完成的。 寫入: 1 File aFile=new File(

01. Java對象的序列序列簡介

語音 log -c object height 通訊 圖片 二進制 進程    Java對象的序列化與反序列化 ; 給大家講解一下什麽是序列化 & 反序列化      當兩個進程進行遠程通訊的時候,彼此相互可以發送各種類型的數據,如文本,圖片,語音和視頻等無論是任何

JAVA基礎之序列序列

步驟 per 文件 color 字節 [] ati input des 序列化和反序列化:   把對象轉化為字節序列的過程稱為序列化;   把字節序列恢復為對象的過程稱為對象的反序列化; 方法:   Java.io.ObjectOutputStream代表對象的輸出流,wr

Java基礎-IO流對象之序列序列

span 作者 創作 style -s 反序列化 ont 對象 io流                 Java基礎-IO流對象之序列化與反序列化                                     作者:尹正傑 版權聲明:原創作品,謝絕轉載!否則將追究

Apache Avro 序列序列 (Java 實現)

Avro像兩個交流一樣要找一個互相能理解的語言, 在國內為普通話, 跑國外多用英語相通, 兩個進程間通信也需要找一個大家都能理解的數據格式. 簡單的如 JSON, XML, 那是自我描述性格式, XML 有 Schema 定義, 但尚無正式的 JSON Schema 規範. 在講求效率的場合, 純文本式的數據

JSON 序列序列(-)泛型 及 java.lang.reflect.Type

限定 完成 ica 所有 void 數據類型 HR ble DC Type及其子接口的來歷 泛型出現之前的類型 沒有泛型的時候,只有原始類型。此時,所有的原始類型都通過字節碼文件類Class類進行抽象。Class類的一個具體對象就代表一個指定的原始類型。 泛型出現之後的類型

基礎 | Java序列序列的底層實現

在深拷貝與淺拷貝中,提到可以採用「序列化與反序列化」的方式來實現深拷貝,今天主要來填一下序列化的坑。 其中,序列化是一種物件持久化的手段,普遍應用於網路傳輸和遠端方法呼叫(RMI)等場景中,建議關注。 什麼是Java序列化和反序列化? 參考答案: 在Java中

JAVA基礎 之 關於序列序列

序列化Serializable,是指將JAVA物件轉換為字元序列的過程,將物件的各屬性儲存起來,在適當的時候獲取並使用。 反序列化是和序列化相反的過程,就是把字元序列轉化為物件的過程。 在JAVA編碼中被廣泛提及,主要應用在以下情況中: 1.持久化儲存資料; 2.程序間的遠端通訊。

java序列序列的問題

java序列化是將java物件轉換為位元組序列的過程,變成計算機能夠儲存的二進位制序列       反序列化是將位元組序列恢復成java物件的過程 1.當兩個Java程序進行通訊時,能否實現程序間的物件傳送呢?答案是可以的。如何做到呢?這就需要Java序列化與反

Java序列序列講解

1、什麼是Java序列化和反序列化? Java序列化是指把Java物件轉換為位元組序列的過程;而Java反序列化是指把位元組序列恢復為Java物件的過程。 2、為什麼要使用序列化和反序列化?      當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資

初識 Java序列序列

轉載-原文出處: Hollis 序列化是一種物件持久化的手段。普遍應用在網路傳輸、RMI等場景中。本文通過分析ArrayList的序列化來介紹Java序列化的相關內容。主要涉及到以下幾個問題: 怎麼實現Java的序列化 為什麼實現了java.io.Serializable接口才能被序列化

java提高篇——java物件的序列序列

      把物件轉換為位元組序列的過程稱為物件的序列化 。    把位元組序列恢復為物件的過程稱為物件的反序列化 。   物件的序列化主要有兩種用途:   1) 把物件的位元組序列永久地儲存到 硬碟 上,通常存放在一個檔案中;   2

【劍指offer】Java實現-序列序列化二叉樹

題目描述 實現兩個函式,分別用來序列化和反序列化二叉樹 思路 序列化:把一棵二叉樹按照某種遍歷方式的結果以某種格式儲存為字串,從而使得記憶體中簡歷起來的二叉樹可以持久儲存 反序列化:根據某

Java---序列序列(ObjectOutputStream/ObjectInputStream)

一:什麼是序列化與反序列化 序列化: 將記憶體中的物件變為二進位制位元組流的形式進行傳輸或儲存在文字中; 並不是所有物件都要被序列化, 一般物件要進行傳輸需要被序列化; 物件若要被序列化輸出,該類需要實現Serializable介面。即只有實現Serializable

Fastjson:Java高效能JSON庫,序列序列

Fastjson是一個Java語言編寫的高效能功能完善的JSON庫。它採用一種“假定有序快速匹配”的演算法,把JSON Parse的效能提升到極致,是目前Java語言中最快的JSON庫。Fastjson介面簡單易用,已經被廣泛使用在快取序列化、協議互動、Web輸出、And

java序列序列總結

很多商業專案用到資料庫、記憶體對映檔案和普通檔案來完成專案中的序列化處理的需求,但是這些方法很少會依靠於Java序列化。本文也不是用來解釋序列化的,而是一起來看看面試中有關序列化的問題,這些問題你很有可能不瞭解。“Java序列化指的是將物件轉換程位元組格式並將物件狀態儲存在檔案中,通常是.ser副檔名的檔案。