浅析若干Java序列化工具

在Java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有:

  1. 采用java对象的序列化和反序列化
  2. 把对象包装成JSON字符串传输
  3. Google工具protoBuf的开源

本文章所需要的序列化jar包都可以下载:http://download.csdn.net/detail/u013256816/9439971

为了便于说明各个做法的区别,分别对这三种做法进行阐述。 对UserVo对象进行序列化,class UserVo如下:

package serialize;
import java.util.List;

public class UserVo
{
    private String name;
    private int age;
    private List<UserVo> friends;
    //此处省略Getter和Setter方法
}

初始化一个UserVo实例:

UserVo user = new UserVo();
user.setName("zzh");
user.setAge(18);

UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);

List<UserVo> friends = new ArrayList<UserVo>();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);

采用java对象的序列化和反序列化

这里简单说明一下java序列化所占用字节大小,具体可以参考http://blog.csdn.net/u013256816/article/details/50474678

 ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(user);
        oos.flush();
        oos.close();
        System.out.println(os.toByteArray().length);

序列化大小:205.
优点:java原生支持,不需要提供第三方的类库,使用比较简单。缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。


把对象包装成JSON字符串传输

JSON工具类有许多种,这里列出三个比较流行的json工具类:Jackson,Gson,FastJson.

1.开源的Jackson

Jackson社区相对比较活跃,更新速度也比较快。Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题。Jackson对于复杂类型的bean转换Json,转换的json格式不是标准的Json格式。

package serialize.json;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import serialize.UserVo;

public class JacksonTest
{
    private UserVo user = null;
    private JsonGenerator jsonGenerator = null;
    private ObjectMapper objectMapper = null;

    @Before
    public void init()
    {
        user = new UserVo();
        user.setName("zzh");
        user.setAge(18);

        UserVo f1 = new UserVo();
        f1.setName("jj");
        f1.setAge(17);
        UserVo f2 = new UserVo();
        f2.setName("qq");
        f2.setAge(19);

        List<UserVo> friends = new ArrayList<UserVo>();
        friends.add(f1);
        friends.add(f2);
        user.setFriends(friends);

        objectMapper = new ObjectMapper();
        try
        {
            jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(System.out,JsonEncoding.UTF8);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    @After
    public void destory()
    {
        try
        {
            if(jsonGenerator != null)
            {
                jsonGenerator.flush();
            }
            if(!jsonGenerator.isClosed())
            {
                jsonGenerator.close();
            }
            jsonGenerator = null;
            objectMapper = null;
            user = null;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    @Test
    public void writeJson()
    {
        try
        {
            jsonGenerator.writeObject(user);
            System.out.println();
            System.out.println(objectMapper.writeValueAsBytes(user).length);
           // System.out.println(objectMapper.writeValueAsString(user).length());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    @Test 
    public void readJson()
    {
        String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17,\"friends\":null},{\"name\":\"qq\",\"age\":19,\"friends\":null}]}";
        UserVo uservo = null;
        try
        {
            uservo = objectMapper.readValue(serString, UserVo.class);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        System.out.println(uservo.getName());
    }
}

序列化大小:111.
注意到这里Jackson会输出null,在Jackson的2.x版本中可以通过设置而使其不输出null的字段。

2. Google的Gson

Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。而在使用这种对象转换之前需先创建好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。类里面只要有get和set方法,Gson完全可以将复杂类型的json到bean或bean到json的转换,是JSON解析的神器。Gson在功能上面无可挑剔,但是性能上面比FastJson有所差距。

package serialize.json;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import serialize.UserVo;

public class GsonTest
{
    private UserVo user = null;

    @Before
    public void init()
    {
        user = new UserVo();
        user.setName("zzh");
        user.setAge(18);

        UserVo f1 = new UserVo();
        f1.setName("jj");
        f1.setAge(17);
        UserVo f2 = new UserVo();
        f2.setName("qq");
        f2.setAge(19);

        List<UserVo> friends = new ArrayList<UserVo>();
        friends.add(f1);
        friends.add(f2);
        user.setFriends(friends);
    }

    @Test
    public void writeJson()
    {
        try
        {
            String str = Gson.class.newInstance().toJson(user);//一行就可以搞定!!!
            System.out.println(str);
            System.out.println(str.length());
        }
        catch (InstantiationException | IllegalAccessException e)
        {
            e.printStackTrace();
        }
    }

    @Test public void readJson()
    {
        String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
        try
        {
            UserVo userVo = Gson.class.newInstance().fromJson(serString, UserVo.class);
            System.out.println(userVo.getName());
        }
        catch (JsonSyntaxException | InstantiationException | IllegalAccessException e)
        {
            e.printStackTrace();
        }
    }
}

序列化大小:81.

Gson和Jackson的区别是:如果你的应用经常会处理大的JSON文件,那么Jackson应该是你的菜。GSON在大文件上表现得相当吃力。如果你主要是处理小文件请求,比如某个微服务或者分布式架构的初始化,那么GSON当是首选。Jackson在小文件上的表现则不如人意。

3. 阿里巴巴的FastJson

Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。
FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。

 package serialize.json;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.alibaba.fastjson.JSON;

import serialize.UserVo;

public class FastJsonTest
{
    private UserVo user = null;

    @Before
    public void init()
    {
        user = new UserVo();
        user.setName("zzh");
        user.setAge(18);

        UserVo f1 = new UserVo();
        f1.setName("jj");
        f1.setAge(17);
        UserVo f2 = new UserVo();
        f2.setName("qq");
        f2.setAge(19);

        List<UserVo> friends = new ArrayList<UserVo>();
        friends.add(f1);
        friends.add(f2);
        user.setFriends(friends);
    }

    @Test public void writeJson()
    {
        String str = JSON.toJSONString(user);
        System.out.println(str);
        System.out.println(str.length());
    }

    @Test public void readJson()
    {
        String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
        UserVo userVo = JSON.parseObject(serString,UserVo.class);
        System.out.println(userVo.getName());
    }
}

注:如果只是功能要求,没有性能要求,可以使用google的Gson,如果有性能上面的要求可以使用Gson将bean转换json确保数据的正确,使用FastJson将Json转换Bean。


Google工具protoBuf

protocol buffers 是google内部得一种传输协议,目前项目已经开源。它定义了一种紧凑得可扩展得二进制协议格式,适合网络传输,并且针对多个语言有不同得版本可供选择。
protoBuf优点:1.性能好,效率高;2.代码生成机制,数据解析类自动生成;3.支持向前兼容和向后兼容;4.支持多种编程语言;5.字节数很小,适合网络传输节省io。缺点:1.应用不够广;2.二进制格式导致可读性差;3.缺乏自描述;
protoBuf是需要编译工具的,这里用的是window的系统。需要下载proto.exe和protobuf-java-2.4.1.jar;
将proto.exe放在当前工程目录下,然后编辑.proto文件,命名为UserVo.proto,如下所示:

package serialize.protobuf;

option java_package = "serialize.protobuf";
option java_outer_classname="UserVoProtos";

message UserVo
{
    optional string name = 1;
    optional int32 age = 2;
    repeated serialize.protobuf.UserVo friends = 3;
}

在命令行中利用protoc工具生成builder类:
这里写图片描述
看到生成了UserVoProtos.java,由于这个java文件有1千行左右,篇幅限制不便罗列。
序列化和反序列化测试代码:

package serialize.protobuf;

import org.junit.Before;
import org.junit.Test;

import com.google.protobuf.InvalidProtocolBufferException;

public class ProtoBufTest
{
    UserVoProtos.UserVo.Builder user = null;

    @Before public void init()
    {
        user = UserVoProtos.UserVo.newBuilder();
        user.setName("zzh");
        user.setAge(18);

        UserVoProtos.UserVo.Builder f1 = UserVoProtos.UserVo.newBuilder();
        f1.setName("jj");
        f1.setAge(17);

        UserVoProtos.UserVo.Builder f2 = UserVoProtos.UserVo.newBuilder();
        f2.setName("qq");
        f2.setAge(19);

        user.addFriends(f1);
        user.addFriends(f2);
    }

    @Test public void doSeri()
    {
        UserVoProtos.UserVo vo = user.build();
        byte[] v = vo.toByteArray();
        for(byte b:v)
        {
            System.out.printf("%02X ",b);
        }
        System.out.println();
        System.out.println(v.length);
    }

    @Test public void doDeSeri()
    {
        byte[] v = new byte[]{0x0A, 0x03, 0x7A, 0x7A, 0x68, 0x10, 0x12, 0x1A, 0x06, 0x0A, 0x02, 0x6A, 0x6A, 0x10, 0x11, 0x1A, 0x06, 0x0A, 0x02, 0x71, 0x71, 0x10, 0x13};
        try
        {
            UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(v);
            System.out.println(uvo.getName());
        }
        catch (InvalidProtocolBufferException e)
        {
            e.printStackTrace();
        }
    }
}

序列化大小:23.
工作机制:proto文件是对数据的一个描述,包括字段名称,类型,字节中的位置。protoc工具读取proto文件生成对应builder代码的类库。protoc xxxxx –java_out=xxxxxx 生成java类库。builder类根据自己的算法把数据序列化成字节流,或者把字节流根据反射的原理反序列化成对象。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。proto文件中的字段类型和java中的对应关系:详见:https://developers.google.com/protocol-buffers/docs/proto.

.proto Type java Type c++ Type
double double double
float float float
int32 int int32
int64 long int64
uint32 int uint32
unint64 long uint64
sint32 int int32
sint64 long int64
fixed32 int uint32
fixed64 long uint64
sfixed32 int int32
sfixed64 long int64
bool boolean bool
string String string
bytes byte string

注:protobuf的一个缺点是需要数据结构的预编译过程,首先要编写.proto格式的配置文件,再通过protobuf提供的工具生成各种语言响应的代码。由于java具有反射和动态代码生成的能力,这个预编译过程不是必须的,可以在代码执行时来实现。有个protostuff(http://code.google.com/p/protostuff/)已经实现了这个功能。protostuff基于Google protobuf,但是提供了更多的功能和更简易的用法。其中,protostuff-runtime实现了无需预编译对java bean进行protobuf序列化/反序列化的能力。protostuff-runtime的局限是序列化前需预先传入schema,反序列化不负责对象的创建只负责复制,因而必须提供默认构造函数。此外,protostuff还可以按照protobuf的配置序列化成json/yaml/xml等格式。这里不做详述,有兴趣的朋友可以参考相关资料。

总结:

方式 优点 缺点
JSON 跨语言、格式清晰一目了然 字节数比较大,需要第三方类库
Object Serialize java原生方法不依赖外部类库 字节数大,不能跨语言
Google protobuf 跨语言、字节数比较少 编写.proto配置用protoc工具生成对应的代码


相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部