Java:對一個對象序列化和反序列化的簡單實現(xiàn)

名詞解釋

序列化:將Java對象轉(zhuǎn)化成字節(jié)的過程

反序列化:將字節(jié)轉(zhuǎn)化成Java對象的過程

字節(jié):1字節(jié)(byte)= 8bit,bit就是計算機(jī)認(rèn)識的二進(jìn)制

序列化的作用

Java對象是在Java虛擬機(jī)中使用的盗扒,一旦Java進(jìn)程結(jié)束跪楞,對象就會消失,要將只有虛擬機(jī)才認(rèn)識的對象侣灶,保存在磁盤中甸祭,必須將對象轉(zhuǎn)化成字節(jié)。

  1. 在RPC中的用處:序列化將對象轉(zhuǎn)換為字節(jié)流褥影,然后通過網(wǎng)絡(luò)傳輸進(jìn)行發(fā)送
  2. 保存對象的狀態(tài):當(dāng)Java進(jìn)程需要重啟時池户,可以將對象序列化后保存在文件中,對象的狀態(tài)不會因為進(jìn)程的關(guān)閉而丟失

如何進(jìn)行序列化

基本數(shù)據(jù)類型轉(zhuǎn)為字節(jié)的思路

對于有多個字節(jié)的數(shù)據(jù)凡怎,用移位運算符校焦,將每8位進(jìn)行移位,用一個字節(jié)保存

  • Int類型:一個int有4個字節(jié)统倒,可以創(chuàng)建一個長度為4的字節(jié)數(shù)組進(jìn)行保存(short寨典,long類似)
  • char類型:一個char有2個字節(jié),用相應(yīng)長度的字節(jié)數(shù)組保存后房匆,反序列化時再強(qiáng)制轉(zhuǎn)化為char
  • String類型:String的值主要是一個char數(shù)組耸成,創(chuàng)建一個大小為char數(shù)組兩倍的字節(jié)數(shù)組進(jìn)行保存,反序列化時再轉(zhuǎn)化為String
  • Double和Float類型:過程比較復(fù)雜(沒學(xué)會)浴鸿,建議直接調(diào)用工具類

一個字節(jié)和其他類型的轉(zhuǎn)換工具類

import java.nio.ByteBuffer;

public class ByteUtils {

    public static byte[] short2bytes(short v) {
        byte[] b = new byte[4];
        b[1] = (byte) v;
        b[0] = (byte) (v >>> 8);
        return b;
    }

    public static byte[] String2bytes(String str){
        char[] chars = str.toCharArray();
        byte[] charByte = new byte[chars.length*2];
        for (int i = 0; i < chars.length; i++) {
            charByte[i*2] = (byte) (chars[i] >>> 8);
            charByte[i*2+1] = (byte) (chars[i]);
        }
        return charByte;
    }

    public static byte[] int2bytes(int v) {
        byte[] b = new byte[4];
        b[3] = (byte) v;
        b[2] = (byte) (v >>> 8);
        b[1] = (byte) (v >>> 16);
        b[0] = (byte) (v >>> 24);
        return b;
    }

    public static byte[] long2bytes(long v) {
        byte[] b = new byte[8];
        b[7] = (byte) v;
        b[6] = (byte) (v >>> 8);
        b[5] = (byte) (v >>> 16);
        b[4] = (byte) (v >>> 24);
        b[3] = (byte) (v >>> 32);
        b[2] = (byte) (v >>> 40);
        b[1] = (byte) (v >>> 48);
        b[0] = (byte) (v >>> 56);
        return b;
    }

    public static byte[] double2bytes(double d){
        long lValue = Double.doubleToLongBits(d);
        byte[] bytes = long2bytes(lValue);
        return bytes;
    }

    public static int bytes2Int_BE(byte[] bytes) {
        if(bytes.length < 4){
            return -1;
        }
        int iRst = (bytes[0] << 24) & 0xFF;
        iRst |= (bytes[1] << 16) & 0xFF;
        iRst |= (bytes[2] << 8) & 0xFF;
        iRst |= bytes[3] & 0xFF;
        return iRst;
    }

    public static long bytes2long(byte[] b) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.put(b, 0, b.length);
        buffer.flip();// need flip
        return buffer.getLong();
    }

    public static String bytes2String(byte[] bytes){
        char[] chars = new char[bytes.length/2];
        for (int i = 0; i < chars.length; i++) {
            chars[i] = (char) ((bytes[i*2] << 8) | bytes[i*2+1]);
        }
        return new String(chars);
    }

    public static float byte2Float(byte[] bytes){
        Integer iValue = bytes2Int_BE(bytes);
        float dValue = Float.intBitsToFloat(iValue);
        return dValue;
    }

    public static double byte2Double(byte[] bytes){
        Long lValue = bytes2long(bytes);
        double dValue = Double.longBitsToDouble(lValue);
        return dValue;
    }

}

View Code

如何序列化對象

其實序列化就是為了反序列化井氢,保存對象之后必然要讀取對象,所以 站在反序列化的角度去研究序列化

得到一串字節(jié)流之后岳链,要如何轉(zhuǎn)換成對象

  • 要通過反射創(chuàng)建對象花竞,首先要知道對象的類名稱,然后調(diào)用類的默認(rèn)構(gòu)造函數(shù)

  • 要識別類名稱掸哑,得知道類名稱是由哪些字節(jié)轉(zhuǎn)換的

  • 要有一個值存放類名稱的字節(jié)長度约急,長度前最好放一個標(biāo)記

  • 給對象的字段賦值,各種類型都不一樣苗分,需要識別

  • 需要數(shù)據(jù)類型的字節(jié)長度烤宙,根據(jù)長度去識別字節(jié)數(shù)組中的一部分

  • 將字節(jié)轉(zhuǎn)換成指定的數(shù)據(jù)類型

  • 通過反射給對象的字段賦值

序列化的代碼實現(xiàn)如下

  • 這里沒有將對象的類型寫入字節(jié)數(shù)組,因為反序列化的方法會傳入一個class參數(shù)俭嘁,可以直接構(gòu)造對象
 1 public static byte[] serialize(Object object) throws IllegalAccessException, ClassNotFoundException {
 2         
 3         // 對象序列化后的字節(jié)數(shù)組
 4         byte[] objectByte = new byte[1024];
 5         // 通過移動的下標(biāo)變量不斷往數(shù)組添加數(shù)據(jù)
 6         int index = 0;
 7         // 標(biāo)記后面的字節(jié)可以轉(zhuǎn)化成對象
 8         objectByte[index] = SerializationNum.SERIALIZATION_OBJECT_NUM;
 9         
10         Class clazz = object.getClass();
11         // 遍歷所有字段,將字段的值轉(zhuǎn)化為字節(jié)
12         Field[] fields = clazz.getDeclaredFields();
13         // 一開始的標(biāo)記占了一個位置
14         int len = 1;
15         for (Field field : fields) {
16 
17             // 將private屬性設(shè)置為可訪問
18             field.setAccessible(true);
19 
20             // 每次移動下標(biāo),給后面的數(shù)據(jù)騰地方
21             index += len;
22             // 不同的類型服猪,不同的轉(zhuǎn)化方式
23             Class fieldClass = field.getType();
24             // 每種類型對應(yīng)一個標(biāo)記
25             byte magicNum = SerializationNum.getNum(fieldClass);
26 
27             byte[] valueByte = new byte[0];
28             switch (magicNum){
29                 case SerializationNum.INTEGER_NUM:
30                     // 反射獲取值
31                     Integer iValue = (Integer) field.get(object);
32                     // int類型是4個字節(jié)
33                     len = 4;
34                     // 轉(zhuǎn)化成一個長度為4的字節(jié)數(shù)組
35                     valueByte = ByteUtils.int2bytes(iValue);
36                     break;
37 
38                 case SerializationNum.LONG_NUM:
39                     long longValue = field.getLong(object);
40                     len = 8;
41                    valueByte = ByteUtils.long2bytes(longValue);
42                     break;
43 
44                 case SerializationNum.STRING_NUM:
45                     String sValue = (String) field.get(object);
46                     valueByte = ByteUtils.String2bytes(sValue);
47                     len = valueByte.length;
48                     break;
49 
50                 default:
51                     break;
52             }
53             // 將類型和長度都添加字節(jié)數(shù)組中
54             objectByte[index++] = magicNum;
55             objectByte[index++] = (byte) len;
56             // 轉(zhuǎn)化后的字節(jié)復(fù)制到對象字節(jié)數(shù)組
57             System.arraycopy(valueByte,0,objectByte,index,len);
58         }
59 
60         index += len;
61         // 對象已經(jīng)整個被轉(zhuǎn)化完畢的標(biāo)記
62         objectByte[index] = SerializationNum.FINISH_NUM;
63 
64         return objectByte;
65     }

反序列化過程實現(xiàn)

 1 public static Object deserialize(byte[] bytes,Class clazz) throws InstantiationException, IllegalAccessException {
 2 
 3         int index = 0;
 4         // 識別首部供填,確保是能夠反序列化成對象的字節(jié)
 5         if (bytes[index] != SerializationNum.SERIALIZATION_OBJECT_NUM) {
 6             return null;
 7         }
 8 
 9         // 使用默認(rèn)構(gòu)造方法
10         Object obj = clazz.newInstance();
11         // 跳過最開始的魔數(shù)
12         byte len = 1;
13 
14         // 一個個給對象的字段賦值
15         for (Field declaredField : clazz.getDeclaredFields()) {
16             declaredField.setAccessible(true);
17             // 每次移動下標(biāo)
18             index += len;
19 
20             // 不同的類型,不同的轉(zhuǎn)化方式
21             Class fieldClass = declaredField.getType();
22 
23             byte magicNum = SerializationNum.getNum(fieldClass);
24 
25             // 防止數(shù)組越界
26             if (index >= bytes.length || bytes[index] != magicNum){
27                 continue;
28             }
29 
30             Object value = null;
31             // 先獲取需要的字節(jié)長度
32              len = bytes[++index];
33             // 創(chuàng)建一個新數(shù)組去作為方法參數(shù)罢猪,如果不采用這種傳參方式近她,也許能節(jié)約這個空間
34             byte[] srcBytes = new byte[len];
35             System.arraycopy(bytes,++index,srcBytes,0,len);
36 
37             switch (magicNum){
38                 case SerializationNum.INTEGER_NUM:
39 
40                     value = ByteUtils.bytes2Int_BE(srcBytes);
41                     break;
42 
43                 case SerializationNum.LONG_NUM:
44 
45                     value = ByteUtils.bytes2long(srcBytes);
46                     break;
47 
48                 case SerializationNum.STRING_NUM:
49 
50                    value = ByteUtils.bytes2String(srcBytes);
51                    break;
52 
53                 case SerializationNum.DOUBLE_NUM:
54                     value = ByteUtils.byte2Double(srcBytes);
55                     break;
56 
57                 default:
58                     break;
59             }
60             // 將值賦給對象
61             declaredField.set(obj,value);
62         }
63         return obj;
64     }

對各種基本數(shù)據(jù)類型進(jìn)行特殊標(biāo)記的工具類

public class SerializationNum {

    public static final byte BOOLEAN_NUM = 0x01;
    public static final byte SHORT_NUM = 0x02;
    public static final byte INTEGER_NUM = 0x03;
    public static final byte LONG_NUM = 0x04;
    public static final byte CHAR_NUM = 0x05;
    public static final byte FLOAT_NUM = 0x06;
    public static final byte DOUBLE_NUM = 0x07;
    public static final byte STRING_NUM = 0x08;
    public static final byte OBJECT_NUM = 0x09;
    public static final byte SERIALIZATION_OBJECT_NUM = 0x10;
    public static final byte FINISH_NUM = 0x30;

    public static byte getNum(Class clazz){
        if (clazz == String.class) {
            return STRING_NUM;
        }
        if (clazz == Integer.class) {

            return INTEGER_NUM;
        }
        if (clazz == Double.class) {

            return DOUBLE_NUM;
        }
        if (clazz == Boolean.class) {
            return BOOLEAN_NUM;
        }
        if (clazz == Float.class) {

            return FINISH_NUM;
        }
        if (clazz == Long.class) {

            return LONG_NUM;
        }

        return 0x77;
    }

}

View Code

測試代碼

 1 public static void main(String[] args) throws IOException {
 2         Entity entity = new Entity();
 3 
 4         entity.setName("name");
 5         entity.setNum(5);
 6         entity.setId(5L);
 7         entity.setMoney(100.55);
 8         
 9         try {
10             System.out.println("可以將對象序列號后的字節(jié)重新反序列化成對象");
11            byte[] objByte = serialize(entity);
12             Entity entity1 = (Entity) deserialize(objByte,Entity.class);
13             System.out.println(entity1);
14 
15 
16             System.out.println("保存在文件的字節(jié),取出來后也可以反序列成對象");
17             FileOutputStream outputStream = new FileOutputStream("text.out");
18             FileInputStream inputStream = new FileInputStream("text.out");
19             byte[] fileBytes = new byte[1024];
20 
21             outputStream.write(objByte);
22             inputStream.read(fileBytes);
23 
24             Entity entity2 = (Entity) deserialize(fileBytes,Entity.class);
25             System.out.println(entity2);
26 
27         } catch (IllegalAccessException | ClassNotFoundException | InstantiationException e) {
28             System.out.println("類不能被構(gòu)造");
29             e.printStackTrace();
30         }
31 
32     }

測試結(jié)果:

Java:對一個對象序列化和反序列化的簡單實現(xiàn)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膳帕,一起剝皮案震驚了整個濱河市粘捎,隨后出現(xiàn)的幾起案子薇缅,更是在濱河造成了極大的恐慌,老刑警劉巖攒磨,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泳桦,死亡現(xiàn)場離奇詭異,居然都是意外死亡娩缰,警方通過查閱死者的電腦和手機(jī)灸撰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拼坎,“玉大人浮毯,你說我怎么就攤上這事√┘Γ” “怎么了债蓝?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盛龄。 經(jīng)常有香客問我饰迹,道長,這世上最難降的妖魔是什么讯嫂? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任蹦锋,我火速辦了婚禮,結(jié)果婚禮上欧芽,老公的妹妹穿的比我還像新娘莉掂。我一直安慰自己,他們只是感情好千扔,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布憎妙。 她就那樣靜靜地躺著,像睡著了一般曲楚。 火紅的嫁衣襯著肌膚如雪厘唾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天龙誊,我揣著相機(jī)與錄音抚垃,去河邊找鬼。 笑死趟大,一個胖子當(dāng)著我的面吹牛鹤树,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逊朽,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼罕伯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叽讳?” 一聲冷哼從身側(cè)響起追他,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤坟募,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邑狸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體懈糯,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年推溃,在試婚紗的時候發(fā)現(xiàn)自己被綠了昂利。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡铁坎,死狀恐怖蜂奸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硬萍,我是刑警寧澤扩所,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站朴乖,受9級特大地震影響祖屏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜买羞,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一袁勺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畜普,春花似錦期丰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舶衬,卻和暖如春埠通,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逛犹。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工端辱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虽画。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓舞蔽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狸捕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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