0

0

Java中常用的序列化方式有哪些?以Kryo、Protostuff和Hessian为例讲解它们的实现原理。

王林

王林

发布时间:2023-05-07 21:01:07

|

1109人浏览过

|

来源于亿速云

转载

    前言

    前段时间在写rpc框架的时候用到了kryo、hessian、protostuff三种序列化方式。但是当时因为急于实现功能,就只是简单的的看了一下如何使用这三种序列化方式,并没有去深入研究各自的特性,以及优点和缺点。知道现在就将rpc框架写完了之后,才有时间静下心来对三种方式做一个对比,总结。

    Kryo、Hessain、Protostuff都是第三方开源的序列化/反序列化框架,要了解其各自的特性,我们首先需要知道序列化/反序列化是什么:

    序列化:就是将对象转化成字节序列的过程。反序列化:就是讲字节序列转化成对象的过程。

    seriallization 序列化 : 将对象转化为便于传输的格式, 常见的序列化格式:二进制格式,字节数组,json字符串,xml字符串。

    deseriallization 反序列化:将序列化的数据恢复为对象的过程

    如果对序列化相关概念还不是很清楚的同学可以参照美团技术团队的序列化和反序列化

    立即学习Java免费学习笔记(深入)”;

    性能对比

    前期准备

    • 我们先创建一个新的Maven项目

    • 然后导入依赖

    
        org.junit.jupiter
        junit-jupiter-api
        5.8.2
        test
    
    
    
        org.projectlombok
        lombok
        1.18.20
    
    
    
        com.esotericsoftware
        kryo-shaded
        4.0.2
    
    
        commons-codec
        commons-codec
        1.10
    
    
    
        io.protostuff
        protostuff-core
        1.7.2
    
    
        io.protostuff
        protostuff-runtime
        1.7.2
    
    
    
        com.caucho
        hessian
        4.0.62
    

    工具类:

    kryo

    package cuit.pymjl.utils;
    import com.esotericsoftware.kryo.Kryo;
    import com.esotericsoftware.kryo.io.Input;
    import com.esotericsoftware.kryo.io.Output;
    import org.apache.commons.codec.binary.Base64;
    import org.objenesis.strategy.StdInstantiatorStrategy;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.UnsupportedEncodingException;
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/4/18 20:07
     **/
    @SuppressWarnings("all")
    public class KryoUtils {
        private static final String DEFAULT_ENCODING = "UTF-8";
    
        //每个线程的 Kryo 实例
        private static final ThreadLocal KRYO_LOCAL = new ThreadLocal() {
            @Override
            protected Kryo initialValue() {
                Kryo kryo = new Kryo();
    
                /**
                 * 不要轻易改变这里的配置!更改之后,序列化的格式就会发生变化,
                 * 上线的同时就必须清除 Redis 里的所有缓存,
                 * 否则那些缓存再回来反序列化的时候,就会报错
                 */
                //支持对象循环引用(否则会栈溢出)
                kryo.setReferences(true); //默认值就是 true,添加此行的目的是为了提醒维护者,不要改变这个配置
    
                //不强制要求注册类(注册行为无法保证多个 JVM 内同一个类的注册编号相同;而且业务系统中大量的 Class 也难以一一注册)
                kryo.setRegistrationRequired(false); //默认值就是 false,添加此行的目的是为了提醒维护者,不要改变这个配置
    
                //Fix the NPE bug when deserializing Collections.
                ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy())
                        .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());
    
                return kryo;
            }
        };
        /**
         * 获得当前线程的 Kryo 实例
         *
         * @return 当前线程的 Kryo 实例
         */
        public static Kryo getInstance() {
            return KRYO_LOCAL.get();
        }
    
        //-----------------------------------------------
        //          序列化/反序列化对象,及类型信息
        //          序列化的结果里,包含类型的信息
        //          反序列化时不再需要提供类型
        //-----------------------------------------------
    
        /**
         * 将对象【及类型】序列化为字节数组
         *
         * @param obj 任意对象
         * @param  对象的类型
         * @return 序列化后的字节数组
         */
        public static  byte[] writeToByteArray(T obj) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Output output = new Output(byteArrayOutputStream);
    
            Kryo kryo = getInstance();
            kryo.writeClassAndObject(output, obj);
            output.flush();
    
            return byteArrayOutputStream.toByteArray();
        }
        /**
         * 将对象【及类型】序列化为 String
         * 利用了 Base64 编码
         *
         * @param obj 任意对象
         * @param  对象的类型
         * @return 序列化后的字符串
         */
        public static  String writeToString(T obj) {
            try {
                return new String(Base64.encodeBase64(writeToByteArray(obj)), DEFAULT_ENCODING);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
        /**
         * 将字节数组反序列化为原对象
         *
         * @param byteArray writeToByteArray 方法序列化后的字节数组
         * @param        原对象的类型
         * @return 原对象
         */
        @SuppressWarnings("unchecked")
        public static  T readFromByteArray(byte[] byteArray) {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
            Input input = new Input(byteArrayInputStream);
    
            Kryo kryo = getInstance();
            return (T) kryo.readClassAndObject(input);
        }
        /**
         * 将 String 反序列化为原对象
         * 利用了 Base64 编码
         *
         * @param str writeToString 方法序列化后的字符串
         * @param  原对象的类型
         * @return 原对象
         */
        public static  T readFromString(String str) {
            try {
                return readFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)));
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
        //-----------------------------------------------
        //          只序列化/反序列化对象
        //          序列化的结果里,不包含类型的信息
        //-----------------------------------------------
    
        /**
         * 将对象序列化为字节数组
         *
         * @param obj 任意对象
         * @param  对象的类型
         * @return 序列化后的字节数组
         */
        public static  byte[] writeObjectToByteArray(T obj) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Output output = new Output(byteArrayOutputStream);
            Kryo kryo = getInstance();
            kryo.writeObject(output, obj);
            output.flush();
            return byteArrayOutputStream.toByteArray();
        }
    
        /**
         * 将对象序列化为 String
         * 利用了 Base64 编码
         *
         * @param obj 任意对象
         * @param  对象的类型
         * @return 序列化后的字符串
         */
        public static  String writeObjectToString(T obj) {
            try {
                return new String(Base64.encodeBase64(writeObjectToByteArray(obj)), DEFAULT_ENCODING);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
        /**
         * 将字节数组反序列化为原对象
         *
         * @param byteArray writeToByteArray 方法序列化后的字节数组
         * @param clazz     原对象的 Class
         * @param        原对象的类型
         * @return 原对象
         */
        @SuppressWarnings("unchecked")
        public static  T readObjectFromByteArray(byte[] byteArray, Class clazz) {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
            Input input = new Input(byteArrayInputStream);
    
            Kryo kryo = getInstance();
            return kryo.readObject(input, clazz);
        }
        /**
         * 将 String 反序列化为原对象
         * 利用了 Base64 编码
         *
         * @param str   writeToString 方法序列化后的字符串
         * @param clazz 原对象的 Class
         * @param    原对象的类型
         * @return 原对象
         */
        public static  T readObjectFromString(String str, Class clazz) {
            try {
                return readObjectFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)), clazz);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    Hessian

    package cuit.pymjl.utils;
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/2 12:39
     **/
    public class HessianUtils {
        /**
         * 序列化
         *
         * @param obj obj
         * @return {@code byte[]}
         */
        public static byte[] serialize(Object obj) {
            Hessian2Output ho = null;
            ByteArrayOutputStream baos = null;
            try {
                baos = new ByteArrayOutputStream();
                ho = new Hessian2Output(baos);
                ho.writeObject(obj);
                ho.flush();
                return baos.toByteArray();
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new RuntimeException("serialize failed");
            } finally {
                if (null != ho) {
                    try {
                        ho.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != baos) {
                    try {
                        baos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        /**
         * 反序列化
         *
         * @param bytes 字节
         * @param clazz clazz
         * @return {@code T}
         */
        public static   T deserialize(byte[] bytes, Class clazz) {
            Hessian2Input hi = null;
            ByteArrayInputStream bais = null;
            try {
                bais = new ByteArrayInputStream(bytes);
                hi = new Hessian2Input(bais);
                Object o = hi.readObject();
                return clazz.cast(o);
            } catch (Exception ex) {
                throw new RuntimeException("deserialize failed");
            } finally {
                if (null != hi) {
                    try {
                        hi.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != bais) {
                    try {
                        bais.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    Protostuff

    package cuit.pymjl.utils;
    import io.protostuff.LinkedBuffer;
    import io.protostuff.ProtostuffIOUtil;
    import io.protostuff.Schema;
    import io.protostuff.runtime.RuntimeSchema;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/6/28 21:00
     **/
    public class ProtostuffUtils {
        /**
         * 避免每次序列化都重新申请Buffer空间
         * 这个字段表示,申请一个内存空间用户缓存,LinkedBuffer.DEFAULT_BUFFER_SIZE表示申请了默认大小的空间512个字节,
         * 我们也可以使用MIN_BUFFER_SIZE,表示256个字节。
         */
        private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        /**
         * 缓存Schema
         * 这个字段表示缓存的Schema。那这个Schema是什么呢?就是一个组织结构,就好比是数据库中的表、视图等等这样的组织机构,
         * 在这里表示的就是序列化对象的结构。
         */
        private static final Map, Schema> SCHEMA_CACHE = new ConcurrentHashMap<>();
    
        /**
         * 序列化方法,把指定对象序列化成字节数组
         *
         * @param obj 对象
         * @return byte[]
         */
        @SuppressWarnings("unchecked")
        public static  byte[] serialize(T obj) {
            Class clazz = (Class) obj.getClass();
            Schema schema = getSchema(clazz);
            byte[] data;
            try {
                data = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
            } finally {
                BUFFER.clear();
            }
            return data;
        }
    
        /**
         * 反序列化方法,将字节数组反序列化成指定Class类型
         *
         * @param data  字节数组
         * @param clazz 字节码
         * @return
         */
        public static  T deserialize(byte[] data, Class clazz) {
            Schema schema = getSchema(clazz);
            T obj = schema.newMessage();
            ProtostuffIOUtil.mergeFrom(data, obj, schema);
            return obj;
        }
        @SuppressWarnings("unchecked")
        private static  Schema getSchema(Class clazz) {
            Schema schema = (Schema) SCHEMA_CACHE.get(clazz);
            if (schema == null) {
                schema = RuntimeSchema.getSchema(clazz);
                if (schema == null) {
                    SCHEMA_CACHE.put(clazz, schema);
                }
            }
            return schema;
        }
    }

    创建一个实体类进行测试:

    package cuit.pymjl.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.io.Serial;
    import java.io.Serializable;
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/2 12:32
     **/
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student implements Serializable {
        @Serial
        private static final long serialVersionUID = -91809837793898L;
    
        private String name;
        private String password;
        private int age;
        private String address;
        private String phone;
    }

    序列化后字节所占空间大小比较

    编写测试类:

    Cogram
    Cogram

    使用AI帮你做会议笔记,跟踪行动项目

    下载
    public class MainTest {
        @Test
        void testLength() {
            Student student = new Student("pymjl", "123456", 18, "北京", "123456789");
            int kryoLength = KryoUtils.writeObjectToByteArray(student).length;
            int hessianLength = HessianUtils.serialize(student).length;
            int protostuffLength = ProtostuffUtils.serialize(student).length;
            System.out.println("kryoLength: " + kryoLength);
            System.out.println("hessianLength: " + hessianLength);
            System.out.println("protostuffLength: " + protostuffLength);
        }
    }

    运行截图:

    Java Kryo,Protostuff,Hessian序列化方式是什么

    由图可知,Hessian序列化后字节所占的空间都显著比其他两种方式要大得多

    其他比较

    • Hessian使用固定长度存储int和long,而kryo使用变长的int和long保证这种基本数据类型序列化后尽量小,实际应用中,很大的数据不会经常出现。

    • Kryo进行序列化的时候,需要传入完整类名或者利用 register() 提前将类注册到Kryo上,其类与一个int型的ID相关联,序列中只存放这个ID,因此序列体积就更小,而Hessian则是将所有类字段信息都放入序列化字节数组中,直接利用字节数组进行反序列化,不需要其他参与,因为存的东西多处理速度就会慢点

    • Kryo使用不需要实现Serializable接口,Hessian则需实现

    • Kryo数据类的字段增、减,序列化和反序列化时无法兼容,而Hessian则兼容,Protostuff是只能在末尾添加新字段才兼容

    • Kryo和Hessian使用涉及到的数据类中必须拥有无参构造函数

    • Hessian会把复杂对象的所有属性存储在一个Map中进行序列化。所以在父类、子类存在同名成员变量的情况下,Hessian序列化时,先序列化子类,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖

    • Kryo不是线程安全的,要通过ThreadLocal或者创建Kryo线程池来保证线程安全,而Protostuff则是线程安全的

    • Protostuff和Kryo序列化的格式有相似之处,都是利用一个标记来记录字段类型,因此序列化出来体积都比较小

    小结

      优点 缺点
    Kryo 速度快,序列化后体积小 跨语言支持较复杂
    Hessian 默认支持跨语言 较慢
    Protostuff 速度快,基于protobuf 需静态编译
    Protostuff-Runtime 无需静态编译,但序列化前需预先传入schema 不支持无默认构造函数的类,反序列化时需用户自己初始化序列化后的对象,其只负责将该对象进行赋值
    Java 使用方便,可序列化所有类 速度慢,占空间

    相关文章

    java速学教程(入门到精通)
    java速学教程(入门到精通)

    java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

    下载

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    相关专题

    更多
    java
    java

    Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

    802

    2023.06.15

    java正则表达式语法
    java正则表达式语法

    java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

    722

    2023.07.05

    java自学难吗
    java自学难吗

    Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

    727

    2023.07.31

    java配置jdk环境变量
    java配置jdk环境变量

    Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

    395

    2023.08.01

    java保留两位小数
    java保留两位小数

    Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

    398

    2023.08.02

    java基本数据类型
    java基本数据类型

    java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

    445

    2023.08.02

    java有什么用
    java有什么用

    java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

    428

    2023.08.02

    java在线网站
    java在线网站

    Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

    16861

    2023.08.03

    桌面文件位置介绍
    桌面文件位置介绍

    本专题整合了桌面文件相关教程,阅读专题下面的文章了解更多内容。

    0

    2025.12.30

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    Kotlin 教程
    Kotlin 教程

    共23课时 | 2.1万人学习

    C# 教程
    C# 教程

    共94课时 | 5.6万人学习

    Java 教程
    Java 教程

    共578课时 | 39.6万人学习

    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号