• Java的序列化


    写在前面

    本文看下序列化和反序列化相关的内容。

    源码

    1:为什么,什么是序列化和反序列化

    Java对象是在jvm的堆中的,而堆其实就是一块内存,如果jvm重启数据将会丢失,当我们希望jvm重启也不要丢失某些对象,或者是需要将某些对象传递到其他服务器(rpc有没有!!!)时就需要使用到序列化和反序列化,因为序列化就是将Java对象转换为文件,而反序列化就是加载文件并生成对象在堆中。
    在这里插入图片描述

    2:Java的序列化和反序列化

    Java想要序列化和反序列化,必须实现java.io.Serializable接口,并给变量serialVersionUID赋值,该值用来标识Java类文件的版本。如下序列化和反序列化的例子:

    @SneakyThrows
    private static void javaDeserialize() {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("d:\\test\\xxx.obj"));
        Student student = (Student) objectInputStream.readObject();
        System.out.println("java反序列化student完成");
        System.out.println(student);
    }
    
    @SneakyThrows
    private static void javaSerialize() throws IOException {
        Student student = new Student();
        student.setName("张三");
        student.setAge(99);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("d:\\test\\xxx.obj"));
        // 如果没有实现java.io.Serializable接口将会抛出异常java.io.NotSerializableException
        objectOutputStream.writeObject(student);
        objectOutputStream.close();
        System.out.println("java序列化student完成");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    java序列化的问题:

    1:无法跨平台
        如Java对象序列化的结果反序列化为python的对象,这样就限制了其不适合某些使用场景,如rpc的场景中就无法使用这种序列化方式,因为rpc需要支持异构的系统
    2:序列化的文件大
        这样占用磁盘大,网络传输速度慢,占用带宽,反序列化的速度也慢,这样就限制了其不适合某些使用场景,如rpc,rpc需要尽量快的序列化和反序列化速度,以提高性能
    3:序列化的速度慢
        还是因为其序列化结果的内容多
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上的问题我们可以使用专门的序列化框架来解决,如hessian。

    3:hessian的序列化和反序列化

    dubbo 默认使用的是该序列化方式,将来可能会优化成性能更优的序列化方式如kryo,fst等。

    hessian支持语言无关的序列化和反序列化,并且速度更快,序列化的结果更小,如下:

    private void hessianSerialize() {
        Student stu = new Student("hessian",1);
        byte[] obj = serialize(stu);
        System.out.println("hessian serialize result length = "+obj.length);
        byte[] obj2 = serialize2(stu);
        System.out.println("hessian2 serialize result length = "+obj2.length);
        byte[] other = jdkSerialize(stu);
        System.out.println("jdk serialize result length = "+other.length);
        Student student = deserialize2(obj2);
        System.out.println("deserialize result entity is "+student);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    具体看文章头源码。
    运行结果如下:

    hessian serialize result length = 65
    hessian2 serialize result length = 59
    jdk serialize result length = 101
    deserialize result entity is Student(name=hessian, age=1)
    
    • 1
    • 2
    • 3
    • 4

    可以看到结果的大小jdk序列化,所以如果工作中有这种需求,建议使用hessian2。

    4:arvo的序列化和反序列化

    使用步骤如下:

    1:定义.avsc描述文件
    2:通过avro-tool.jar,以.avsc描述文件作为输入生成pojo
    3:通过avro.jar的API进行序列化(生成.avro文件)和反序列化
    
    • 1
    • 2
    • 3

    首先我们需要定义IDL,命名为User.avsc:

    {
        "namespace": "dongshi.daddy.seriablize.avro",
        "type": "record",
        "name": "User",
        "fields": [
            {"name": "name", "type": "string"},
            {"name": "id", "type": "int"},
            {"name": "salary", "type": "int"},
            {"name": "age", "type": "int"},
            {"name": "address", "type": "string"}
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接着通过avro-tools.jar生成pojo,如下:

    $ java -jar avro-tools-1.8.2.jar compile schema User.avsc res
    Input files to compile:
      User.avsc
    log4j:WARN No appenders could be found for logger (AvroVelocityLogChute).
    log4j:WARN Please initialize the log4j system properly.
    log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后我们将生成的User.java文件拷贝到classpath的dongshi.daddy.seriablize.avro目录,接着就可以执行序列化和反序列化了,首先执行序列化:

    @Test
    public void testAvroSerialize() throws Exception {
    // 声明并初始化User对象
        // 方式一
        User user1 = new User();
        user1.setName("wqbin");
        user1.setId(1);
        user1.setSalary(1000);
        user1.setAge(20);
        user1.setAddress("beijing");
    
        // 方式二 使用构造函数
    // Alternate constructor
        User user2 = new User("wang", 2, 1000, 19, "guangzhou");
    
    // 方式三,使用Build方式
    // Construct via builder
        User user3 = User.newBuilder()
                .setName("bin")
                .setId(3)
                .setAge(21)
                .setSalary(2000)
                .setAddress("shenzhen")
                .build();
        String userDir = System.getProperty("user.dir");
        System.out.println("userDir is: " + userDir);
        String path = userDir + "/User.avro"; // avro文件存放目录
        DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class);
        DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
        dataFileWriter.create(user1.getSchema(), new File(path));
    // 把生成的user对象写入到avro文件
        dataFileWriter.append(user1);
        dataFileWriter.append(user2);
        dataFileWriter.append(user3);
        dataFileWriter.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    生成文件如下:
    在这里插入图片描述

    接着执行反序列化:

    @Test
    public void testAvroDeserialize() throws Exception {
        DatumReader<User> reader = new SpecificDatumReader<User>(User.class);
        DataFileReader<User> dataFileReader = new DataFileReader<User>(new File(System.getProperty("user.dir") + "/User.avro"), reader);
        User user = null;
        while (dataFileReader.hasNext()) {
            user = dataFileReader.next();
            System.out.println(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    输出如下内容则为成功:

    {"name": "wqbin", "id": 1, "salary": 1000, "age": 20, "address": "beijing"}
    {"name": "wang", "id": 2, "salary": 1000, "age": 19, "address": "guangzhou"}
    {"name": "bin", "id": 3, "salary": 2000, "age": 21, "address": "shenzhen"}
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5:kryo的序列化和反序列化

    kryo是底层使用了ASM字节码技术,所以其只能使用在基于JVM的语言上,如Java,scala,kotlin等,接下来看下其如何使用。

    • 首先引入pom:
    <dependency>
        <groupId>com.esotericsoftwaregroupId>
        <artifactId>kryoartifactId>
        <version>5.2.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 序列化:
    @Test
    public void testKrypSerialize() throws Exception {
        Kryo kryo = new Kryo();
        kryo.register(SomeClass.class);
    
        SomeClass someClass = new SomeClass();
        someClass.value = "dongshidaddy";
        Output output = new Output(new FileOutputStream(userDir + "/someCls.bin"));
        kryo.writeObject(output, someClass);
        output.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行后:
    在这里插入图片描述

    • 反序列化
    @Test
    public void testKrypDeserialize() throws Exception {
        Kryo kryo = new Kryo();
        kryo.register(SomeClass.class);
    
        Input input = new Input(new FileInputStream(userDir + "/someCls.bin"));
        SomeClass someClassFromBin = kryo.readObject(input, SomeClass.class);
        System.out.println(someClassFromBin.value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行后:

    dongshidaddy
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3

    6:fst的序列化和反序列化

    java的序列化和反序列化方式,性能优秀(jdk原生序列化速度的10倍,序列化结果体积1/3左右),如果有序列化的需求可以考虑使用。看下如何使用。

    • 引入pom
    <dependency>
        <groupId>de.ruedigermoellergroupId>
        <artifactId>fstartifactId>
        <version>2.04version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 序列化和反序列化
    // fst序列化和反序列化
    @Test
    public void testFstSerializeAndDescrialize() {
        dongshi.daddy.seriablize.fst.User bean = new dongshi.daddy.seriablize.fst.User();
        bean.setUsername("xxxxx");
        bean.setPassword("123456");
        bean.setAge(1000000);
        byte[] byteBean = configuration.asByteArray(bean);
        System.out.println("序列化的字节大小是:" + byteBean.length);
        // 反序列化
        dongshi.daddy.seriablize.fst.User resultBean = (dongshi.daddy.seriablize.fst.User) configuration.asObject(byteBean);
        System.out.println("fst反序列化的结果是:" + resultBean);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出如下内容则为成功:

    序列化的字节大小是:68
    fst反序列化的结果是:User(username=xxxxx, age=1000000, password=123456)
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4

    写在后面

    巨人的肩膀

    再来认识一下 Java 序列化

    Hessian序列化实例

    Avro从入门到入土

    深入浅出序列化(2)——Kryo序列化

    Kryo 和 FST 序列化

  • 相关阅读:
    企业工程项目管理系统源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)
    (undone) 如何计算 Hessian Matrix 海森矩阵 海塞矩阵
    搜索技术领域的“奥林匹克”,飞桨支持“第二届百度搜索创新大赛”正式启动!...
    Java-修改 String 指定位置的字符最全方法总结(StringBuilder 和 StringBuffer 的使用以及区别)
    Python02--python中的缩进,注释和模块
    idea中启动maven项目报错-java: 程序包lombok.extern.slf4j不存在问题如何解决
    git merge 如何撤销
    java每日一记 —— 谈谈反射
    机试算法学习
    红黑树详解
  • 原文地址:https://blog.csdn.net/wang0907/article/details/120670934