名词解释
序列化:将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 flipreturn 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参数,可以直接构造对象
public static byte[] serialize(Object object) throws IllegalAccessException, ClassNotFoundException {// 对象序列化后的字节数组byte[] objectByte = new byte[1024];// 通过移动的下标变量不断往数组添加数据int index = 0;// 标记后面的字节可以转化成对象objectByte[index] = SerializationNum.SERIALIZATION_OBJECT_NUM;Class clazz = object.getClass();// 遍历所有字段,将字段的值转化为字节Field[] fields = clazz.getDeclaredFields();// 一开始的标记占了一个位置int len = 1;for (Field field : fields) {// 将private属性设置为可访问field.setAccessible(true);// 每次移动下标,给后面的数据腾地方index += len;// 不同的类型,不同的转化方式Class fieldClass = field.getType();// 每种类型对应一个标记byte magicNum = SerializationNum.getNum(fieldClass);byte[] valueByte = new byte[0];switch (magicNum){case SerializationNum.INTEGER_NUM:// 反射获取值Integer iValue = (Integer) field.get(object);// int类型是4个字节len = 4;// 转化成一个长度为4的字节数组valueByte = ByteUtils.int2bytes(iValue);break;case SerializationNum.LONG_NUM:long longValue = field.getLong(object);len = 8;valueByte = ByteUtils.long2bytes(longValue);break;case SerializationNum.STRING_NUM:String sValue = (String) field.get(object);valueByte = ByteUtils.String2bytes(sValue);len = valueByte.length;break;default:break;}// 将类型和长度都添加字节数组中objectByte[index++] = magicNum;objectByte[index++] = (byte) len;// 转化后的字节复制到对象字节数组System.arraycopy(valueByte,0,objectByte,index,len);}index += len;// 对象已经整个被转化完毕的标记objectByte[index] = SerializationNum.FINISH_NUM;return objectByte;}
反序列化过程实现
public static Object deserialize(byte[] bytes,Class clazz) throws InstantiationException, IllegalAccessException {int index = 0;// 识别首部,确保是能够反序列化成对象的字节if (bytes[index] != SerializationNum.SERIALIZATION_OBJECT_NUM) {return null;}// 使用默认构造方法Object obj = clazz.newInstance();// 跳过最开始的魔数byte len = 1;// 一个个给对象的字段赋值for (Field declaredField : clazz.getDeclaredFields()) {declaredField.setAccessible(true);// 每次移动下标index += len;// 不同的类型,不同的转化方式Class fieldClass = declaredField.getType();byte magicNum = SerializationNum.getNum(fieldClass);// 防止数组越界if (index >= bytes.length || bytes[index] != magicNum){continue;}Object value = null;// 先获取需要的字节长度len = bytes[++index];// 创建一个新数组去作为方法参数,如果不采用这种传参方式,也许能节约这个空间byte[] srcBytes = new byte[len];System.arraycopy(bytes,++index,srcBytes,0,len);switch (magicNum){case SerializationNum.INTEGER_NUM:value = ByteUtils.bytes2Int_BE(srcBytes);break;case SerializationNum.LONG_NUM:value = ByteUtils.bytes2long(srcBytes);break;case SerializationNum.STRING_NUM:value = ByteUtils.bytes2String(srcBytes);break;case SerializationNum.DOUBLE_NUM:value = ByteUtils.byte2Double(srcBytes);break;default:break;}// 将值赋给对象declaredField.set(obj,value);}return obj;}
对各种基本数据类型进行特殊标记的工具类
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
测试代码
public static void main(String[] args) throws IOException {Entity entity = new Entity();entity.setName(\"name\");entity.setNum(5);entity.setId(5L);entity.setMoney(100.55);try {System.out.println(\"可以将对象序列号后的字节重新反序列化成对象\");byte[] objByte = serialize(entity);Entity entity1 = (Entity) deserialize(objByte,Entity.class);System.out.println(entity1);System.out.println(\"保存在文件的字节,取出来后也可以反序列成对象\");FileOutputStream outputStream = new FileOutputStream(\"text.out\");FileInputStream inputStream = new FileInputStream(\"text.out\");byte[] fileBytes = new byte[1024];outputStream.write(objByte);inputStream.read(fileBytes);Entity entity2 = (Entity) deserialize(fileBytes,Entity.class);System.out.println(entity2);} catch (IllegalAccessException | ClassNotFoundException | InstantiationException e) {System.out.println(\"类不能被构造\");e.printStackTrace();}}
测试结果: