Java-對象序列化

原文: java基礎複習(18)-對象序列化

對象序列化的深刻探究

我研究了一下jdk的實現,但願對你有所幫助,研究狀況以下: html

在我本機測試代碼,查看序列化的文件guo.txt,在ultraEdit下, 用本地編碼看會是一串亂碼,可是用十六進制查看,就能夠發現規律,文件內容以下: AC ED 00 05 7A 00 00 02 FD 11 00 0A 0D 00 0A 07.....(後面內容省略) 每次執行,發現前面的AC ED 00 05總會存在。先解釋這個吧。 我在此只是想以代碼進一步證實: 對於建立一個對象輸出流時,查看構造器的代碼以下: java

Java代碼api

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
    bout = new BlockDataOutputStream(out);
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);  
    enableOverride = false;
    writeStreamHeader();
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}

writeStreamHeader(); //這句很重要,即只要你針對文件打開一個對象輸出流,它就會向其中寫入4個字節的內容。能夠查看到寫入的內容是:

Java代碼數組

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}
寫入的兩個常量在接口ObjectStreamConstants定義以下:

Java代碼緩存

public interface ObjectStreamConstants {  
/**
 * Magic number that is written to the stream header.
 */
final static short STREAM_MAGIC = (short)0xaced;

/**
 * Version number that is written to the stream header.
 */  
final static short STREAM_VERSION = 5;

從註釋就能夠知道,ACED只是寫入序列化文件的一個固定標記,起標識做用。 oracle

BlockDataOutputStream類的writeShort方法在內部又使用了Bits類的方法,具體感興趣的同窗能夠本身查看源代碼。因爲寫進的是short,在java中short佔兩個字節,因此文件頭就變成了AC ED 00 05。

而後解釋7A。其實當你測試比較少的數據時,當寫入的數據長度小於255個時,文件的前面部分會有另一種結果: AC ED 00 05 77 05 01 10 13 0A 06 ide

boutOjbectOutStream的私有靜態內部類BlockDataOutputStream的實例。 該類持有 測試

Java代碼this

/** buffer for writing block data headers */
private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
// 緩存數據塊的頭部信息

/** buffer for writing general/block data */
private final byte[] buf = new byte[MAX_BLOCK_SIZE];
// 該緩衝數組保存要寫入的數據,但長度大於MAX_BLOCK_SIZE(1024)時,會刷新緩衝將內容寫入文件。

在api中也清楚的註明,摘錄入下: 編碼

基本數據(不包括 serializable 字段和 externalizable 數據)以塊數據記錄的形式寫入 ObjectOutputStream 中。塊數據記錄由頭部和數據組成。 塊數據部分包括標記和跟在部分後面的字節數。連續的基本寫入數據被合併在一個塊數據記錄中。塊數據記錄的分塊因子爲 1024 字節。每一個塊數據記錄都將填滿 1024 字節,或者在終止塊數據模式時被寫入。

源碼以下:

Java代碼

private void writeBlockHeader(int len) throws IOException {  
    if (len <= 0xFF) {  
        hbuf[0] = TC_BLOCKDATA;
        hbuf[1] = (byte) len;
        out.write(hbuf, 0, 2);
    } else {
        hbuf[0] = TC_BLOCKDATALONG;
        Bits.putInt(hbuf, 1, len);
        out.write(hbuf, 0, 5);
    }
}

以上代碼中的TC_BLOCKDATA也定義在接口ObjectStreamConstants中。

Java代碼

/**
 * Block of optional data. Byte following tag indicates number
 * of bytes in this block data.
 */

final static byte TC_BLOCKDATA = (byte) 0x77; // 跟在77後面的字節記錄一個數據塊中實際寫入的字節數,這裏只有5個字節的數據內容,因爲只有一個字節記錄,因此最多隻能有 255個字節的數據部分,數據長度該句代碼hbuf[1] = (byte) len;產生。

/**
 * long Block data. The long following the tag indicates the
 * number of bytes in this block data.
 */
final static byte TC_BLOCKDATALONG= (byte)0x7A; // 一樣的後面跟的四個字節來記錄寫入的數據長度
每次數據塊緩衝區滿時(大於1024)就會刷新緩衝區,將塊頭和數據寫入文件保存,因此保存的數據較多時,你會發如今文件的後面部分也會出現屢次77 xx或7A xx,那是屢次寫入引發的 。每次刷新緩衝後,會重置塊緩衝buf偏移pos爲0,從頭寫入。數據長度有該句代碼產生 Bits.putInt(hbuf, 1, len);

繼續查看類Bits的該方法:

Java代碼

static void putInt(byte[] b, int off, int val) {
    b[off + 3] = (byte) (val >>> 0);
    b[off + 2] = (byte) (val >>> 8);
    b[off + 1] = (byte) (val >>> 16);
    b[off + 0] = (byte) (val >>> 24);
}

該方法相信你們應該看得懂,其實就是把長度(int)的每一個字節取出放到了hbuf中和7A一塊兒作頭部。

還有一點就是,若是你調用輸出流的write方法後,卻不去關閉輸出流,當數據量小於1024個字節時,文件中只會包含序列化的文件頭: AC ED 00 05,而沒有真正寫入其餘數據.數據量大於1024個字節的話,因爲超過容量會刷新緩衝區,文件固然就包含數據咯。因此咱們在寫入較少數據的時候,注意要關閉輸出流,這樣就能夠在關閉時,將緩衝區的數據寫入到文件中去。

還有部分非關鍵代碼就不貼出來了,想研究的同窗直接查看就是了。以上內容只是本人針對源碼結合測試得出的結論,不足之處,還請你們批評指正。

針對對象序列化,會將序列化的類和字段的基本信息保存在序列化文件中,也能夠想見反序列化總會依賴必定信息吧,不可能直接針對一個普通文本文件就反序列化,這樣就欠考慮了,呵呵。它也會首先根據文件頭(即AC ED 00 05)來斷定是否是一個序列化文件,若是不是就直接拋出異常。

針對對象序列化的問題,內容補充:
  • 當你想序列化的類實現了Serialiazble接口時,序列化後再次反序列化時,不會調用該類的默認構造器。
  • 而你的類若是實現了Externalizable接口時,在反序列化產生實例時,會調用默認構造器,初始化成員變量。
參考:
補充:

The following symbols in java.io.ObjectStreamConstants define the terminal and constant values expected in a stream.

final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_NULL = (byte)0x70;
final static byte TC_REFERENCE = (byte)0x71;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
final static byte TC_STRING = (byte)0x74;
final static byte TC_ARRAY = (byte)0x75;
final static byte TC_CLASS = (byte)0x76;
final static byte TC_BLOCKDATA = (byte)0x77;
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_RESET = (byte)0x79;
final static byte TC_BLOCKDATALONG = (byte)0x7A;
final static byte TC_EXCEPTION = (byte)0x7B;
final static byte TC_LONGSTRING = (byte) 0x7C;
final static byte TC_PROXYCLASSDESC = (byte) 0x7D;
final static byte TC_ENUM = (byte) 0x7E;
final static  int   baseWireHandle = 0x7E0000;

The flag byte classDescFlags may include values of

final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
 final static byte SC_BLOCK_DATA = 0x08;    //if SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
final static byte SC_ENUM = 0x10;
相關文章
相關標籤/搜索