浅谈序列化

目录

 

序列化简介:

 

JDK的序列化

Protobuf序列化

Thrift框架中的序列化

Protobuf与Thrift框架对比

MessagePack序列化框架

JSON与XML

JSON

fastjson

jackson

Gson

XML

dom4j

sax

jdk内置


序列化与反序列化简介:

序列化也叫编码。就是把对象变成二进制(字节数组),序列化主要有两个目的:

  • 网络传输
  • 对象技久化

   

反列化也叫编解码,就是把二进制变成对象

JDK的序列化

JDK从1.1版本提供了序列化,无需添加额外的类库,只需要POJO实现Serializable接口即可通过ObjectInputStream、ObjectOutputStream读取或写出,但是jdk自身的序列化性能太低,编流太大不适应一些高并发通信场合网络通信,并且无法跨语言

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long userId;
    private String username;
    private String realName;
    private Integer age;

    public User(Long userId, String username, String realName, Integer age) {
        this.userId = userId;
        this.username = username;
        this.realName = realName;
        this.age = age;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
public class SerializableTest {
    public static void main(String[] args) throws IOException {

        User user = new User(100000L, "admin", "小三", 18);
        ByteBuffer  byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.putLong(user.getUserId());
        byteBuffer.put(user.getUsername().getBytes());
        byteBuffer.put(user.getRealName().getBytes());
        byteBuffer.putInt(user.getAge());
        byteBuffer.flip();
        System.out.println("使用ByteBuffer编码后的编流大小:" + byteBuffer.remaining());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(user);
        oos.flush();
        oos.close();
        System.out.println("使用jdk序列化后的编流大小:" + baos.toByteArray().length);
    }
}

由测试结果可以看出,JDK序列化机制编码后的码流大小是采用手工二进制编码的13倍

 

接下来进测试一下序列化性能:

public class SerializableTest {
    public static void main(String[] args) throws IOException {

        User user = new User(100000L, "admin", "小三", 18);
        final  int times = 10000;
        long startTime = System.currentTimeMillis();

        for (int i = 0; i <= times; i++){
            ByteBuffer  byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.putLong(user.getUserId());
            byteBuffer.put(user.getUsername().getBytes());
            byteBuffer.put(user.getRealName().getBytes());
            byteBuffer.putInt(user.getAge());

        }
        long endime = System.currentTimeMillis();
        System.out.println("使用ByteBuffer编码后花费的时间:" + (endime - startTime));
        startTime = System.currentTimeMillis();
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        for (int i = 0; i <= times; i++){
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(user);
            oos.flush();
            oos.close();
        }
        endime = System.currentTimeMillis();
        System.out.println("使用jdk序列化后的花费的时间:" + (endime - startTime));
        
    }
}

由测试结果可以看出,JDK序列化机制编码所花费的时间是采用手工二进制编码的5.7倍

以上两点说明编码后字节数组越大,意味着越占空间,存储的硬件成本越高,在网络传输时占用更大带宽,导致系统呑吐量下降,因此现在RPC框架中都不采用JDK的序列化

 

Protobuf序列化

官方文档:https://developers.google.cn/protocol-buffers 

它将数据结构定义成一个*.proto文件,通过代码生成工具 protoc生成对应的POJO对象和Protbuf相关的对象

 

定义数据结构定义文件:User.proto

syntax = "proto3";
package proto;
option java_package = "com.isaiah.nettydemo.proto";
option java_outer_classname = "User";

message UserMessage{
    int64 userId = 1;
    string username = 2;
    string realName = 3;
    int32 age = 4;
}

在java工程中添加protobuf的jar包

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.10.0</version>
</dependency>

对proto文件进行编译生成java类:protoc User.proto --java_out=/Users/isaiah/Workspace/xlb/NettyDemo/src/main/java

测试:

public class ProtobufTest {
    public static void main(String[] args) throws IOException {
        User.UserMessage userMessage = User.UserMessage.newBuilder()
                .setUserId(10000L)
                .setUsername("admin")
                .setRealName("小三")
                .setAge(18).build();

        byte[] bytes = userMessage.toByteArray();
        System.out.println("使用protobuf序列化后的编流大小:" + bytes.length);

        User.UserMessage u = User.UserMessage.parseFrom(bytes);
        System.out.println(u);
    }
}

 

优点

  1. 跨语言,可自定义数据结构。
  2. 字段被编号,新添加的字段不影响老结构。解决了向后兼容问题。
  3. 自动化生成代码,简单易用。
  4. 二进制消息,效率高,性能高。
  5. Netty等框架集成了该协议,提供了编×××提高开发效率。

缺点

  1. 二进制格式,可读性差(抓包dump后的数据很难看懂)
  2. 对象冗余,字段很多,生成的类较大,占用空间。
  3. 默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持)

总结:简单快速上手,高效兼容性强,维护成本较高。

 

Thrift框架中的序列化

与protobuf类似,Thrift通过IDL描述接口定义数据结构,通过代码生成工具thrift生成对应的POJO代码,它的以下特点:

优点

  1.  序列化和RPC支持一站式解决,比pb更方便
  2. 跨语言,IDL接口定义语言,自动生成多语言文件
  3. 省流量,体积较小
  4. 包含完整的客户端/服务端堆栈,可快速实现RPC
  5. 为服务端提供了多种工作模式,如线程池模型、非阻塞模型

缺点

  1. 早期版本问题较大,0.7以前有兼容性问题
  2. 不支持双通道
  3. rpc方法非线程安全,服务器容易被挂死,需要串行化。
  4. 默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持)
  5. 开发环境、编译较麻烦

总结:跨语言、实现简单,初次使用较麻烦,需要避免使用问题和场景限制。

 

Protobuf与Thrift框架对比

相比Thrift支持更多语言C++, Java, Python, Ruby, Perl, PHP, C#, Erlang, Haskell

  • Thrift的字节码并不紧凑,比如每个字段的id占4个字节,类型占1个字节;而Google Protocol Buffers的字段id和类型占同一个字节,而且对于i32等类型还会使用varint减少数组长度。
  • Thrift生成的Java代码很简洁,代码实现也很简洁;Google Protocol Buffers生成的Java代码动不动就几千行……
  • Thrift不单单是一个序列化协议,更是一个rpc调用框架;从这方面来说,Google Protocol Buffers是完全做不到的。


 

MessagePack序列化框架

 

public class MsgPackTest {
    public static void main(String[] args) throws IOException {
        User user = new User(100000L, "admin", "小三", 18);
        MessagePack messagePack = new MessagePack();
        byte[] buff = messagePack.write(user);
        System.out.println("使用MessagePack序列化后的码流大量:" + buff.length);
        User u = messagePack.read(buff, User.class);
        System.out.println(u);
    }
}

优点

  1. 跨语言,多语言支持(超多)
  2. It’s like JSON.but fast and small.序列化反序列化效率高(比json快一倍),文件体积小,比json小一倍。
  3. 兼容json数据格式

缺点

  1. 缺乏复杂模型支持。msgpack对复杂的数据类型(List、Map)支持的不够,序列化没有问题,但是反序列化回来就很麻烦,尤其是对于java开发人员。
  2. 维护成本较高。msgpack通过value的顺序来定位属性的,需要在不同的语言中都要维护同样的模型以及模型中属性的顺序。
  3. 不支持模型嵌套。msgpack无法支持在模型中包含和嵌套其他自定义的模型(如weibo模型中包含comment的列表)。

总结:高性能但扩展性较差维护成本较高。

 

JSON与XML

json、xml本身是字符串,而protobuf、thrift序列化最终是二进制,在性能上无法相提并论,但在使用上json、xml相比非常简单

json解析框架主要有三个:fastjson、jackson、gson,在java11中已jdk内置json解析的API

 

JSON

优点

  1. 简单易用开发成本低
  2. 跨语言
  3. 轻量级数据交换
  4. 非冗长性(对比xml标签简单括号闭环)

缺点

  1. 体积大,影响高并发
  2. 无版本检查,自己做兼容
  3. 片段的创建和验证过程比一般的XML复杂
  4. 缺乏命名空间导致信息混合

总结:最简单最通用的应用协议,使用广泛,开发效率高,性能相对较低,维护成本较高。

 

fastjson

阿里开发的一个json解析框架,号称速度最快的josn解析库

jackson

spring框架默认使用的序列化框架,特点:功能强大,但是使用上比较复杂

Gson

Gson在安卓开发中应用的比较多,使用上也非常简单

XML

dom4j

sax

jdk内置

编码与反解码也称编组、解编组

 

应用场景分析

json/xml序列化在传输本质上是字符串,适合在系统业务内部,而jdk序列化/protobuf/Thrif是二进制更适合RPC系统间通信传输

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值