Java:對一個對象序列化和反序列化的簡單實現
- 2022 年 1 月 5 日
- 筆記
名詞解釋
序列化:將Java對象轉化成位元組的過程
反序列化:將位元組轉化成Java對象的過程
位元組:1位元組(byte)= 8bit,bit就是電腦認識的二進位
序列化的作用
Java對象是在Java虛擬機中使用的,一旦Java進程結束,對象就會消失,要將只有虛擬機才認識的對象,保存在磁碟中,必須將對象轉化成位元組。
- 在RPC中的用處:序列化將對象轉換為位元組流,然後通過網路傳輸進行發送
- 保存對象的狀態:當Java進程需要重啟時,可以將對象序列化後保存在文件中,對象的狀態不會因為進程的關閉而丟失
如何進行序列化
基本數據類型轉為位元組的思路
對於有多個位元組的數據,用移位運算符,將每8位進行移位,用一個位元組保存
- Int類型:一個int有4個位元組,可以創建一個長度為4的位元組數組進行保存(short,long類似)
- char類型:一個char有2個位元組,用相應長度的位元組數組保存後,反序列化時再強制轉化為char
- String類型:String的值主要是一個char數組,創建一個大小為char數組兩倍的位元組數組進行保存,反序列化時再轉化為String
- Double和Float類型:過程比較複雜(沒學會),建議直接調用工具類
一個位元組和其他類型的轉換工具類


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 }
對各種基本數據類型進行特殊標記的工具類


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 }
測試結果: