Java序列化工具对比

1. Java序列化工具技术原理比较

  • Binary Formats & language-specific ones
    JavaBuiltIn(java原生)、JavaManual(根据成员变量类型,手工写)、FstSerliazationKryo
  • Binary formats-generic language-unspecific ones
    Protobuf(Google)Thrift(Facebook)、 AvroGeneric、Hessian
  • JSON Format
    Jackson、Gson、FastJSON
  • JSON-like:
    CKS (textual JSON-like format)、BSON(JSON-like format with extended datatypes)、JacksonBson、MongoDB
  • XML-based formats
    XmlXStream

java的序列化工具大致就可以分为以上几类,简单概括就分为二进制binary和文本格式(json、xml)两大类。
在速度的对比上一般有如下规律:
binary > textual
language-specific > language-unspecific
而textual中,由json相比xml冗余度更低因此速度上更胜一筹,而json又bson这类textual serialization技术上更成熟,框架的选择上更丰富和优秀。下面重点介绍下Kryo、fast-serialiation、fastjson、protocol-buffer

2. 典型Java序列化工具分析

目前互联网公司广泛使用Protobuf、Thrift、Avro等成熟的序列化解决方案来搭建RPC框架,这些都是久经考验的解决方案。

2.1 Java原生序列化工具

Java本身提供的序列化工具基本上能胜任大多数场景下的序列化任务,关于其序列化机制,这篇文章很细致的解释了(https://blog.csdn.net/zhaozheng7758/article/details/7820018),值得一读。Java自带的序列化工具在序列化过程中需要不仅需要将对象的完整的class name记录下来,还需要把该类的定义也都记录下,包括所有其他引用的类,这会是一笔很大的开销,尤其是仅仅序列化单个对象的时候。正因为java序列化机制会把所有meta-data记录下来,因此当修改了类的所在的包名后,反序列化则会报错。Java自带序列化工具的性能问题总结如下:
一个single object的序列化会 递归地,连同所有成员变量(instsnce variables)一起序列化了,这种默认机制很容易造成不必要的序列化开销。
序列化和反序列化过程需要上面的这种机制去递归并用反射机制去寻找所有成员变量的信息,另外如果没定义自己serialVersionUID的话,那么对象及其他变量都必须自己产生一个。上述过程开销很大。
使用默认序列化机制,所有序列化类定义完整信息都会被记录下来,包括所有包名、父类信息、以及成员变量

2.2 优化过的Java序列化工具

  1. kryo
    kryo根据上述Java原生序列化机制的一些问题,对了很多优化工作,而且提供了很多serializer,甚至封装了Unsafe类型的序列化方式,更多关于Unsafe类型的序列化方式,请参考这里,需要注意的是,jdk1.7以后,默认关闭unsafe的类(sun.misc.Unsafe)包。更多kryo介绍参考kryo的wiki.
  2. fast-serialization
    fst-serialozation相对来说是一个很新的序列化工具,虽然从2-1的评测上来看,速度于kryo有一些差距,但根据本人在生产环境上的场景上测试,效果几乎于kryo一致,都能瞬间反序列化出内容并渲染

2.3 JSON

比较优秀的JSON解析工具的表现还是比较好的,有些json解析工具甚至速度超过了一些二进制的序列化方式。

2.4 Protocol-Buffer

Protocol buffers是一个用来序列化结构化数据的技术,支持多种语言诸如C++、Java以及Python语言,可以使用该技术来持久化数据或者序列化成网络传输的数据。相比较一些其他的XML技术而言,该技术的一个明显特点就是更加节省空间(以二进制流存储)、速度更快以及更加灵活。
另外Protobuf支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议(Presentation Layer),目前并没有一个专门支持Protobuf的RPC框架。

2.5 Thrift

Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。 但是,Thrift并不仅仅是序列化协议,而是一个RPC框架。 相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面, Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用(例如HTTP)。

2.6 Avro

Avro解析性能高并且序列化之后的数据非常简洁,比较适合于高性能的序列化服务。

Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美, JSON格式方便测试阶段的调试。 Avro支持的数据类型非常丰富,包括C++语言里面的union类型。Avro支持JSON格式的IDL和类似于Thrift和Protobuf的IDL(实验阶段),这两者之间可以互转。Schema可以在传输数据的同时发送,加上JSON的自我描述属性,这使得Avro非常适合动态类型语言。 Avro在做文件持久化的时候,一般会和Schema一起存储,所以Avro序列化文件自身具有自我描述属性,所以非常适合于做Hive、Pig和MapReduce的持久化数据格式。对于不同版本的Schema,在进行RPC调用的时候,服务端和客户端可以在握手阶段对Schema进行互相确认,大大提高了最终的数据解析速度。

3.下面介绍几种常用的Java序列化技术使用示例

KryoRegister、FST、Kryo、Gson、Fastjson、JDK

3.1 JDK

public static byte[] serialize(Object obj) {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        byte[] bs = baos.toByteArray();
        baos.close();
        oos.close();

        return bs;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

public static Object deserialize(byte[] bits) {
    try {
        ByteArrayInputStream bais = new ByteArrayInputStream(bits);
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = ois.readObject();

        bais.close();
        ois.close();
        return obj;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}  

3.2 Fastjson

一个JSON库涉及的最基本功能就是序列化和反序列化。Fastjson支持java bean的直接序列化。 使用com.alibaba.fastjson.JSON这个类进行序列化和反序列化。

public static String serialize(Object obj){
    String json = JSON.toJSONString(obj);
    return json;
}
public static Object deserialize(String json, Class<?> clazz){
    Object obj = JSON.parseObject(json, clazz);
    return obj;
}

3.3 FST

FST fast-serialization 是重新实现的 Java 快速对象序列化的开发包。序列化速度更快(2-10倍)、体积更小,而且兼容 JDK 原生的序列化。

Java 快速序列化库 FST 已经发布了 2.0 版本,该版本的包名已经更改,无法平滑升级。另外官方建议为了稳定性考虑还是使用最新的 1.58 版本为好

static FSTConfiguration configuration = FSTConfiguration
           .createDefaultConfiguration();

public static byte[] serialize(Object obj){
     return configuration.asByteArray((Serializable)obj);
}
public static Object deserialize(byte[] sec){
    return configuration.asObject(sec);
}

3.4 Gson

这里采用JSON格式同时使用采用Google的gson进行转义.

static Gson gson = new Gson();

public static String serialize(Object obj){
    String json = gson.toJson(obj);
    return json;
}
public static Object deserialize(String json, Class<?> clazz){
    Object obj = gson.fromJson(json, clazz);
    return obj;
}

3.5 Jackson

Jackson库(http://jackson.codehaus.org),是基于java语言的开源json格式解析工具,整个库(使用最新的2.2版本)包含3个jar包:
jackson-core.jar——核心包(必须),提供基于“流模式”解析的API。
jackson-databind——数据绑定包(可选),提供基于“对象绑定”和“树模型”相关API。
jackson-annotations——注解包(可选),提供注解功能。
相对于java json解析的其他库,诸如json-lib、gson包,Jackson具有以下优点:
功能全面,提供多种模式的json解析方式,“对象绑定”使用方便,利用注解包能为我们开发提供很多便利。
性能较高,“流模式”的解析效率超过绝大多数类似的json包。
核心包:JsonPaser(json流读取),JsonGenerator(json流输出)。
数据绑定包:ObjectMapper(构建树模式和对象绑定模式),JsonNode(树节点)

public static String serialize(Object obj){
    ObjectMapper mapper = new ObjectMapper();
    String json = null;
    try {
        json = mapper.writeValueAsString(obj);
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return json;
}
public static Object deserialize(String json, Class<?> clazz){
    ObjectMapper mapper = new ObjectMapper();
    Object obj = null;
    try {
        obj = mapper.readValue(json, clazz);
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return obj;
}

3.6 Kryo 和 KryoRegister

Kryo的运行速度是java Serializable 的20倍左右
Kryo的文件大小是java Serializable的一半左右
Kryo有两种模式:
一种是先注册(regist),再写对象,即writeObject函数,实际上如果不先注册,在写对象时也会注册,并为class分配一个id。
注意,如果是rpc,则必须两端都按同样的顺序注册,否则会出错,因为必须要明确类对应的唯一id。
另一种是写类名及对象,即writeClassAndObject函数。
writeClassAndObject函数是先写入(-1 + 2)(一个约定的数字),再写入类ID(第一次要先写-1,再写类ID + 类名),写入引用关系(见引用的实现),最后才写真正的数据)。
注意每一次writeClassAndObject调用后信息都会清空,所以不用担心和client交互时会出错。

static Kryo kryo = new Kryo();

public static byte[] serialize(Object obj) {
    byte[] buffer = new byte[2048];
    Output output = new Output(buffer);

    kryo.writeClassAndObject(output, obj);
    byte[] bs = output.toBytes();
    output.close();
    return bs;
}

public static Object deserialize(byte[] src) {
    Input input = new Input(src);
    Object obj = kryo.readClassAndObject(input);
    input.close();
    return obj;
}

register

static Kryo kryo = null;
static{
    kryo = new Kryo();
    kryo.setReferences(false);
    kryo.setRegistrationRequired(false);
    kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
}

public static byte[] serialize(Object obj) {
    kryo.register(obj.getClass());
    byte[] buffer = new byte[2048];
    Output output = new Output(buffer);
    kryo.writeObject(output, obj);
    byte[] bs = output.toBytes();
    output.close();
    return bs;
}

public static Object deserialize(byte[] src, Class<?> clazz) {
    kryo.register(clazz);
    Input input = new Input(src);
    Object obj = kryo.readObject(input, clazz);
    input.close();
    return obj;
}

Object Serializalbe 优点:java原生支持,不需要提供第三方的类库,使用比较简单。缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。
对象在进行序列化和反序列化的时候,必须实现Serializable接口,但并不强制声明唯一的serialVersionUID,是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。

4. 小结

就已有原先使用Java原生序列化方案的系统来说,kryo于fst-serializer是良好的java原生序列化方案替代者,不仅体现再编程简单,而且速度与性能上会有大幅提升,尤其是fst-serializer ,只需替代output/inputstream 即可,性能的提升上也很可观,目前该工具刚出来,稳定性还需要多测测。

如果程序本身就用json格式序列化,则可以考虑引入一个性能优异的json解析库,一般再服务端jackson是广受欢迎的解析库。

protobuffer更多的是一种取代xml的夸语言的消息交换格式,尽快速度很快,但是编程上需要定义消息格式,对成员变量多、业务复杂的javabean来说代价是较为复杂的,对稳定的已有系统来说总体代价较高。

下表是几种方案的各项指标的一个对比

序列化工具 序列化速度 序列化文件大小 编程模型复杂度 社区活跃度 jar包大小
kryo 极快 简单 132kb
fst-serializer 非常简单 246kb
protobuffer 较大 较复杂 稳定 329kb
fastjson 较快 较大 简单 一般 338kb
jackson 一般 较大 简单 稳定 1.1mb
gson 较慢 较大 简单 稳定 189kb

参考:
http://blog.51cto.com/zlfwmm/1761401
https://blog.csdn.net/wodeyuer125/article/details/44495549
https://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html
http://www.javacodegeeks.com/2010/07/java-best-practices-high-performance.html
https://blog.csdn.net/zhaozheng7758/article/details/7820018
http://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

posted @ 2018-07-04 08:17  john8169  阅读(2546)  评论(0编辑  收藏  举报