Java 串行化

Serialization 是把對(duì)象的狀態(tài)轉(zhuǎn)換為字節(jié)流衷恭,同時(shí)字節(jié)流也可以轉(zhuǎn)換為對(duì)象,反向過程叫做 Deserialization

串行化可以把對(duì)象的狀態(tài)保存到文件中鸳慈,也可以通過網(wǎng)絡(luò)傳輸對(duì)象

串行化接口

java.io.Serializable接口是一個(gè)標(biāo)記接口(不含有數(shù)據(jù)和方法)拂募,String和所有的原始數(shù)據(jù)類型的包裝器類都默認(rèn)實(shí)現(xiàn)了該接口

ObjectOutputStream類用來串行化對(duì)象為OutputStream,類字段如果是引用帅刊,對(duì)應(yīng)的引用對(duì)象也需要序列化
ObjectInputStream 類用來反序列化先前串行化的原始數(shù)據(jù)和對(duì)象掂骏,重構(gòu)對(duì)象

可以寫入多個(gè)對(duì)象或原始數(shù)據(jù)類型到輸出流,這些對(duì)象必須從相應(yīng)的ObjectInputstream讀取厚掷,類型和順序應(yīng)該要和寫入的相同

FileOutputStream fos = new FileOutputStream("t.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeInt(12345);
oos.writeObject("Today");
oos.writeObject(new Date());

oos.close();
FileInputStream fis = new FileInputStream("t.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);

int i = ois.readInt();
String today = (String) ois.readObject();
Date date = (Date) ois.readObject();

ois.close();
  • 可以使用try/catch弟灼,處理轉(zhuǎn)換過程中可能出現(xiàn)的異常
  • 使用transient修飾符的字段,不會(huì)被序列化
  • 靜態(tài)字段不會(huì)序列化(serialVersionUID例外)
  • 不是每個(gè)類都可序列化冒黑,有些類是不能序列化的田绑, 例如涉及線程的類
  • 子類實(shí)現(xiàn)Serializable接口而父類未實(shí)現(xiàn)時(shí),父類不會(huì)被序列化
  • 父類實(shí)現(xiàn)序列化抡爹,子類自動(dòng)實(shí)現(xiàn)序列化

如果需要特殊處理序列化和反序列化掩驱,可以在類中自定義序列化方法

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
 private void readObjectNoData()
     throws ObjectStreamException;

為了避免因?yàn)椋琂AVA的序列化機(jī)制采用了一種特殊的算法:

1冬竟、所有保存到磁盤中的對(duì)象都有一個(gè)序列化編號(hào)
2欧穴、當(dāng)程序試圖序列化一個(gè)對(duì)象時(shí),會(huì)先檢查該對(duì)象是否已經(jīng)被序列化過泵殴,只有該對(duì)象從未(在本次虛擬機(jī)中)被序列化涮帘,系統(tǒng)才會(huì)將該對(duì)象轉(zhuǎn)換成字節(jié)序列并輸出
3、如果對(duì)象已經(jīng)被序列化笑诅,程序?qū)⒅苯虞敵鲆粋€(gè)序列化編號(hào)调缨,而不是重新序列化

serialVersionUID

用來確保在反序列化的過程中疮鲫,加載的是同樣的類(序列號(hào)對(duì)應(yīng)的類)

語法:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L;

原因:
有可能序列化一個(gè)對(duì)象到文件中,幾個(gè)月后才在不同的JVM進(jìn)行反序列化弦叶,此時(shí)對(duì)應(yīng)的類可能已經(jīng)改變了俊犯。
如果要反序列化的serialVersionUID不相同,產(chǎn)生異常InvalidClassException伤哺。

生成方式:

  • 顯式聲明燕侠,比如和系統(tǒng)的版本保持一致
  • 自動(dòng)生成,沒有顯式聲明的時(shí)候立莉,進(jìn)行序列化的時(shí)候根據(jù)相關(guān)規(guī)則產(chǎn)生一個(gè)默認(rèn)的serialVersionUID贬循,但是相應(yīng)的計(jì)算對(duì)類的細(xì)節(jié)非常敏感,可能編譯器的實(shí)現(xiàn)而有所不同桃序,所以強(qiáng)烈建議所有可序列化的類都明確聲明serialVersionUID

Externalizable

Serializable接口的子類杖虾,通過特定的兩個(gè)方法來指定要序列化的對(duì)象,而父類直接序列化所有對(duì)象

writeExternal(ObjectOutput out)
readExternal(ObjectInput in)

與父類Serializable的區(qū)別媒熊,反序列化重構(gòu)對(duì)象時(shí)奇适,先通過一個(gè)public的無參數(shù)構(gòu)造函數(shù)創(chuàng)建對(duì)象,再調(diào)用readExternal方法芦鳍,父類是直接通過ObjectInputStream創(chuàng)建的


測試

import java.io.*;

public class Solution {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("/home/wdy/Desktop/test"));
        outputStream.writeObject(new Test(0xBBBBBBBB,"Wang"));
        outputStream.close();
    }

    public static class Test implements Serializable {
        public static final long serialVersionUID = 0xAAAAAAAAAAAAAAAAL;
        int num;
        String name;

        public Test(int num, String name) {
            this.num = num;
            this.name = name;
        }
    }
}

寫出的二進(jìn)制數(shù)據(jù):

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   
00000000: AC ED 00 05 73 72 00 21 63 6F 6D 2E 67 69 74 68    ,m..sr.!com.gith
00000010: 75 62 2E 77 61 6E 67 64 79 31 32 2E 53 6F 6C 75    ub.wangdy12.Solu
00000020: 74 69 6F 6E 24 54 65 73 74 AA AA AA AA AA AA AA    tion$Test*******
00000030: AA 02 00 02 49 00 03 6E 75 6D 4C 00 04 6E 61 6D    *...I..numL..nam
00000040: 65 74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53    et..Ljava/lang/S
00000050: 74 72 69 6E 67 3B 78 70 BB BB BB BB 74 00 04 57    tring;xp;;;;t..W
00000060: 61 6E 67                                           ang


解釋:

序列化會(huì)記錄每個(gè)類的名稱嚷往,字段的名稱和類型,最后才是具體的數(shù)據(jù)類型

ObjectOutputStream初始化時(shí)就會(huì)寫出頭信息:

  • 寫出AC ED表示魔數(shù)柠衅,即文件類型皮仁,寫出版本號(hào)00 05,這兩個(gè)字段都是固定的

寫一個(gè)普通的對(duì)象writeOrdinaryObject

  • 對(duì)象標(biāo)志0x73

類描述信息writeClassDesc

  • 類標(biāo)志0x72菲宴,表示一個(gè)新的類描述
  • 類名稱(modified-utf-8格式贷祈,這里編碼長度為33字節(jié)即0x21,大字節(jié)序?qū)懗鰹閟hort形式喝峦,之后是UTF內(nèi)容势誊,這里全部是單字節(jié)編碼com.github.wangdy12.Solution$Test),序列號(hào)(8個(gè)0xAA
  • 標(biāo)志谣蠢,一個(gè)字節(jié)粟耻,表示不同的序列化類型(例如對(duì)象是否自定義了writeObject方法),這里為0x02
  • 字段數(shù)目眉踱,兩個(gè)字節(jié)挤忙,這里為00 02,即兩個(gè)字段谈喳,接下來處理每個(gè)字段的描述信息
  • int字段的寫出册烈,類型占用一個(gè)字節(jié),對(duì)應(yīng)為I叁执,即49茄厘,接下來是字段名稱,前兩個(gè)字節(jié)表示長度00 03谈宛,后面是具體的名稱num
  • String字段的寫出:簽名第一個(gè)字符對(duì)應(yīng)為L次哈,即0x4C,然后是字段名稱吆录,前兩個(gè)字節(jié)表示長度00 04窑滞,后面是字段名稱name,如果不是原始類型恢筝,再寫出其類型簽名哀卫,這里是Ljava/lang/String;,以writeString寫出其類型簽名撬槽,先寫標(biāo)志0x74此改,然后內(nèi)部再調(diào)用writeUTF以UTF形式寫出,長度0x12侄柔,即18共啃,內(nèi)容Ljava/lang/String;
  • 對(duì)象塊的結(jié)束標(biāo)志0x78
  • 遞歸寫出對(duì)象的父類信息描述,遞歸調(diào)用writeClassDesc暂题,這里為null移剪,寫出0x70

寫出具體的數(shù)據(jù)writeSerialData

  • 如果有自定義的writeObject就調(diào)用,如果沒有使用默認(rèn)的defaultWriteFields寫出薪者,先寫出原始數(shù)據(jù)類型的值纵苛,然后寫出對(duì)象字段中的實(shí)際數(shù)據(jù)

序列化使用的常量位于ObjectStreamConstants類中,內(nèi)部包含一些標(biāo)志位

static final short STREAM_MAGIC = (short)0xaced;
static final short STREAM_VERSION = 5;
static final byte TC_NULL =         (byte)0x70;
static final byte TC_CLASSDESC =    (byte)0x72;
static final byte TC_OBJECT =       (byte)0x73;
static final byte TC_STRING =       (byte)0x74;
static final byte TC_ENDBLOCKDATA = (byte)0x78;

Kryo

一種更高效的的序列化方式言津,相同對(duì)象的序列化攻人,大小大大減小

public class Solution {
    public static void main(String[] args) throws IOException {
        Kryo kryo = new Kryo();
        kryo.register(Test.class);//需要進(jìn)行注冊(cè),不注冊(cè)時(shí)改為 kryo.setRegistrationRequired(false);
        Test test = new Test(0xBBBBBBBB,"Wang");
        Output output = new Output(new FileOutputStream("/home/wdy/Desktop/test-kryo"));
        kryo.writeClassAndObject(output, test);
        output.close();

        Input input = new Input(new FileInputStream("/home/wdy/Desktop/test-kryo"));
        Object object2 = kryo.readClassAndObject(input);
        input.close();
        System.out.println(((Test)object2).num);
    }
}

注冊(cè)后序列化結(jié)果只有10個(gè)字節(jié)悬槽,不包含類型信息贝椿,第一個(gè)字節(jié)是一個(gè)變長int,表示注冊(cè)對(duì)應(yīng)的序號(hào)陷谱,之后四個(gè)字節(jié)表示Wang烙博,且g最后一個(gè)字節(jié)的最高位為1,即最后一個(gè)字節(jié)為負(fù)數(shù)烟逊,表示字符串結(jié)束渣窜,最后五個(gè)字節(jié)是一個(gè)邊長編碼的int,即0xBBBBBBBB

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   
00000000: 0B 57 61 6E E7 89 91 A2 C4 08                      .Wang.."D.

如果不進(jìn)行注冊(cè)宪躯,對(duì)應(yīng)結(jié)果會(huì)記錄類名稱

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   
00000000: 01 00 63 6F 6D 2E 67 69 74 68 75 62 2E 77 61 6E    ..com.github.wan
00000010: 67 64 79 31 32 2E 53 6F 6C 75 74 69 6F 6E 24 54    gdy12.Solution$T
00000020: 65 73 F4 57 61 6E E7 89 91 A2 C4 08                estWang.."D.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乔宿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子访雪,更是在濱河造成了極大的恐慌详瑞,老刑警劉巖掂林,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坝橡,居然都是意外死亡泻帮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門计寇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锣杂,“玉大人,你說我怎么就攤上這事番宁≡” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蝶押,是天一觀的道長踱蠢。 經(jīng)常有香客問我,道長棋电,這世上最難降的妖魔是什么朽基? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮离陶,結(jié)果婚禮上稼虎,老公的妹妹穿的比我還像新娘。我一直安慰自己招刨,他們只是感情好霎俩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沉眶,像睡著了一般打却。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谎倔,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天柳击,我揣著相機(jī)與錄音,去河邊找鬼片习。 笑死捌肴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藕咏。 我是一名探鬼主播状知,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孽查!你這毒婦竟也來了饥悴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎西设,沒想到半個(gè)月后瓣铣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贷揽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年棠笑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擒滑。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腐晾,死狀恐怖叉弦,靈堂內(nèi)的尸體忽然破棺而出丐一,到底是詐尸還是另有隱情,我是刑警寧澤淹冰,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布库车,位于F島的核電站,受9級(jí)特大地震影響樱拴,放射性物質(zhì)發(fā)生泄漏柠衍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一晶乔、第九天 我趴在偏房一處隱蔽的房頂上張望珍坊。 院中可真熱鬧,春花似錦正罢、人聲如沸阵漏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽履怯。三九已至,卻和暖如春裆泳,著一層夾襖步出監(jiān)牢的瞬間叹洲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工工禾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留运提,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓闻葵,卻偏偏與公主長得像糙捺,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笙隙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,863評(píng)論 0 24
  • 一洪灯、 序列化和反序列化概念 Serialization(序列化)是一種將對(duì)象以一連串的字節(jié)描述的過程;反序列化de...
    步積閱讀 1,441評(píng)論 0 10
  • 如果你只知道實(shí)現(xiàn) Serializable 接口的對(duì)象,可以序列化為本地文件签钩。那你最好再閱讀該篇文章掏呼,文章對(duì)序列化...
    jiangmo閱讀 475評(píng)論 0 2
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化,必須實(shí)現(xiàn)Serializable接口铅檩。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,409評(píng)論 1 3
  • 你好憎夷!做夢也想不到我把信寫到五線譜上吧? 五線譜是偶然來的昧旨,你也是偶然來的拾给。不過我給你的信值得寫在五線譜里呢.。但...
    Carina____閱讀 101評(píng)論 0 0