Java序列化详解

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

什么是序列化?什么是反序列化 #

当需要持久化Java对象,比如将Java对象保存在文件中、或者在网络中传输Java对象,这些场景都需要用到序列化

即:

  • 序列化:将数据结构/对象转换成二进制字节流
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于Java,序列化的是对象(Object),也就是实例化后的类(Class)

序列化的目的,是通过网络传输对象,或者说是将对象存储到文件系统、数据库、内存中,如图: ly-20241212141924954

实际场景 #

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化
  • 将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
  • 对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化

序列化协议对于TCP/IP 4层模型的哪一层 #

4层包括,网络接口层,网络层,传输层,应用层 如下图所示:
ly-20241212141925161

OSI七层协议模型中,表示层就是对应用层的用户数据,进行处理转换成二进制流;反过来的话,就是将二进制流转换成应用层的用户数据,即序列化和反序列化,
因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分

常见序列化协议对比 #

kryo 英音 [k’rɪəʊ] ,除了JDK自带的序列化,还有hessiankryoprotostuff

  • JDK自带的序列化,只需要实现java.io.Serializable接口即可

    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    @Builder
    @ToString
    public class RpcRequest implements Serializable {
        private static final long serialVersionUID = 1905122041950251207L;
        private String requestId;
        private String interfaceName;
        private String methodName;
        private Object[] parameters;
        private Class<?>[] paramTypes;
        private RpcMessageTypeEnum rpcMessageTypeEnum;
    }
    

    serialVersionUID用于版本控制,会被写入二进制序列,反序列化如果发现和当前类不一致则会抛出InvalidClassException异常。一般不使用JDK自带序列化,1 不支持跨语言调用 2 性能差,序列化之后字节数组体积过大

  • Kryo 由于变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小字节码体积,代码:

    /**
     * Kryo serialization class, Kryo serialization efficiency is very high, but only compatible with Java language
     *
     * @author shuang.kou
     * @createTime 2020年05月13日 19:29:00
     */
    @Slf4j
    public class KryoSerializer implements Serializer {
    
        /**
         * Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
         */
        private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
            Kryo kryo = new Kryo();
            kryo.register(RpcResponse.class);
            kryo.register(RpcRequest.class);
            return kryo;
        });
    
        @Override
        public byte[] serialize(Object obj) {
            try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                 Output output = new Output(byteArrayOutputStream)) {
                Kryo kryo = kryoThreadLocal.get();
                // Object->byte:将对象序列化为byte数组
                kryo.writeObject(output, obj);
                kryoThreadLocal.remove();
                return output.toBytes();
            } catch (Exception e) {
                throw new SerializeException("Serialization failed");
            }
        }
    
        @Override
        public <T> T deserialize(byte[] bytes, Class<T> clazz) {
            try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
                 Input input = new Input(byteArrayInputStream)) {
                Kryo kryo = kryoThreadLocal.get();
                // byte->Object:从byte数组中反序列化出对象
                Object o = kryo.readObject(input, clazz);
                kryoThreadLocal.remove();
                return clazz.cast(o);
            } catch (Exception e) {
                throw new SerializeException("Deserialization failed");
            }
        }
    
    }
    
  • Protobuf 出自google

  • ProtoStuff,更为易用

  • hessian,轻量级的自定义描述的二进制RPC协议,跨语言,hessian2,为阿里修改过的hessian lite,是dubbo RPC默认启用的序列化方式

  • 总结

    • 如果不需要跨语言可以考虑Kryo
    • Protobuf,ProtoStuff,hessian支持跨语言