Java:對一個物件序列化和反序列化的簡單實現

划水的魚dm發表於2022-01-05

名詞解釋

序列化:將Java物件轉化成位元組的過程

反序列化:將位元組轉化成Java物件的過程

位元組:1位元組(byte)= 8bit,bit就是計算機認識的二進位制

序列化的作用

Java物件是在Java虛擬機器中使用的,一旦Java程式結束,物件就會消失,要將只有虛擬機器才認識的物件,儲存在磁碟中,必須將物件轉化成位元組。

  1. 在RPC中的用處:序列化將物件轉換為位元組流,然後通過網路傳輸進行傳送
  2. 儲存物件的狀態:當Java程式需要重啟時,可以將物件序列化後儲存在檔案中,物件的狀態不會因為程式的關閉而丟失

如何進行序列化

基本資料型別轉為位元組的思路

對於有多個位元組的資料,用移位運算子,將每8位進行移位,用一個位元組儲存

  • Int型別:一個int有4個位元組,可以建立一個長度為4的位元組陣列進行儲存(short,long類似)
  • char型別:一個char有2個位元組,用相應長度的位元組陣列儲存後,反序列化時再強制轉化為char
  • String型別:String的值主要是一個char陣列,建立一個大小為char陣列兩倍的位元組陣列進行儲存,反序列化時再轉化為String
  • Double和Float型別:過程比較複雜(沒學會),建議直接呼叫工具類

一個位元組和其他型別的轉換工具類

Java:對一個物件序列化和反序列化的簡單實現
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

 

如何序列化物件

其實序列化就是為了反序列化,儲存物件之後必然要讀取物件,所以站在反序列化的角度去研究序列化

得到一串位元組流之後,要如何轉換成物件

  • 要通過反射建立物件,首先要知道物件的類名稱,然後呼叫類的預設建構函式
    • 要識別類名稱,得知道類名稱是由哪些位元組轉換的
    • 要有一個值存放類名稱的位元組長度,長度前最好放一個標記
  • 給物件的欄位賦值,各種型別都不一樣,需要識別
    • 需要資料型別的位元組長度,根據長度去識別位元組陣列中的一部分
    • 將位元組轉換成指定的資料型別
    • 通過反射給物件的欄位賦值

序列化的程式碼實現如下

  • 這裡沒有將物件的型別寫入位元組陣列,因為反序列化的方法會傳入一個class引數,可以直接構造物件
 1 public static byte[] serialize(Object object) throws IllegalAccessException, ClassNotFoundException {
 2         
 3         // 物件序列化後的位元組陣列
 4         byte[] objectByte = new byte[1024];
 5         // 通過移動的下標變數不斷往陣列新增資料
 6         int index = 0;
 7         // 標記後面的位元組可以轉化成物件
 8         objectByte[index] = SerializationNum.SERIALIZATION_OBJECT_NUM;
 9         
10         Class clazz = object.getClass();
11         // 遍歷所有欄位,將欄位的值轉化為位元組
12         Field[] fields = clazz.getDeclaredFields();
13         // 一開始的標記佔了一個位置
14         int len = 1;
15         for (Field field : fields) {
16 
17             // 將private屬性設定為可訪問
18             field.setAccessible(true);
19 
20             // 每次移動下標,給後面的資料騰地方
21             index += len;
22             // 不同的型別,不同的轉化方式
23             Class fieldClass = field.getType();
24             // 每種型別對應一個標記
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個位元組
33                     len = 4;
34                     // 轉化成一個長度為4的位元組陣列
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             // 將型別和長度都新增位元組陣列中
54             objectByte[index++] = magicNum;
55             objectByte[index++] = (byte) len;
56             // 轉化後的位元組複製到物件位元組陣列
57             System.arraycopy(valueByte,0,objectByte,index,len);
58         }
59 
60         index += len;
61         // 物件已經整個被轉化完畢的標記
62         objectByte[index] = SerializationNum.FINISH_NUM;
63 
64         return objectByte;
65     }

 

反序列化過程實現 

 1 public static Object deserialize(byte[] bytes,Class clazz) throws InstantiationException, IllegalAccessException {
 2 
 3         int index = 0;
 4         // 識別首部,確保是能夠反序列化成物件的位元組
 5         if (bytes[index] != SerializationNum.SERIALIZATION_OBJECT_NUM) {
 6             return null;
 7         }
 8 
 9         // 使用預設構造方法
10         Object obj = clazz.newInstance();
11         // 跳過最開始的魔數
12         byte len = 1;
13 
14         // 一個個給物件的欄位賦值
15         for (Field declaredField : clazz.getDeclaredFields()) {
16             declaredField.setAccessible(true);
17             // 每次移動下標
18             index += len;
19 
20             // 不同的型別,不同的轉化方式
21             Class fieldClass = declaredField.getType();
22 
23             byte magicNum = SerializationNum.getNum(fieldClass);
24 
25             // 防止陣列越界
26             if (index >= bytes.length || bytes[index] != magicNum){
27                 continue;
28             }
29 
30             Object value = null;
31             // 先獲取需要的位元組長度
32              len = bytes[++index];
33             // 建立一個新陣列去作為方法引數,如果不採用這種傳參方式,也許能節約這個空間
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     }

 

對各種基本資料型別進行特殊標記的工具類

Java:對一個物件序列化和反序列化的簡單實現
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("可以將物件序列號後的位元組重新反序列化成物件");
11            byte[] objByte = serialize(entity);
12             Entity entity1 = (Entity) deserialize(objByte,Entity.class);
13             System.out.println(entity1);
14 
15 
16             System.out.println("儲存在檔案的位元組,取出來後也可以反序列成物件");
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("類不能被構造");
29             e.printStackTrace();
30         }
31 
32     }

 

測試結果:

 

相關文章