您的位置:首页 > 编程语言 > Java开发

Java:对一个对象序列化和反序列化的简单实现

2022-01-05 10:29 405 查看

名词解释

序列化:将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类型:过程比较复杂(没学会),建议直接调用工具类

一个字节和其他类型的转换工具类

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参数,可以直接构造对象
    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();
    }
    
    }

     

    测试结果:

     

  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐