浅析若干Java序列化工具

简介:   在java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有:采用java对象的序列化和反序列化把对象包装成JSON字符串传输Google工具protoBuf的开源 本文章所需要的序列化jar包都可以下载:http://download.csdn.net/detail/u013256816/9439971。

  在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工具生成对应的代码
目录
相关文章
|
3月前
|
人工智能 缓存 监控
使用LangChain4j构建Java AI智能体:让大模型学会使用工具
AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。
1006 1
|
2月前
|
人工智能 监控 Java
Java与AI智能体:构建自主决策与工具调用的智能系统
随着AI智能体技术的快速发展,构建能够自主理解任务、制定计划并执行复杂操作的智能系统已成为新的技术前沿。本文深入探讨如何在Java生态中构建具备工具调用、记忆管理和自主决策能力的AI智能体系统。我们将完整展示从智能体架构设计、工具生态系统、记忆机制到多智能体协作的全流程,为Java开发者提供构建下一代自主智能系统的完整技术方案。
419 4
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
178 1
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
204 1
|
3月前
|
人工智能 Java API
Java AI智能体实战:使用LangChain4j构建能使用工具的AI助手
随着AI技术的发展,AI智能体(Agent)能够通过使用工具来执行复杂任务,从而大幅扩展其能力边界。本文介绍如何在Java中使用LangChain4j框架构建一个能够使用外部工具的AI智能体。我们将通过一个具体示例——一个能获取天气信息和执行数学计算的AI助手,详细讲解如何定义工具、创建智能体并处理执行流程。本文包含完整的代码示例和架构说明,帮助Java开发者快速上手AI智能体的开发。
1123 8
|
8月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
1093 4
|
7月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
263 0
|
6月前
|
Java 数据安全/隐私保护 计算机视觉
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本内容提供Java生成自定义图片的示例代码,涵盖基础图像创建、文本添加及保存功能,适合学习2D图形编程。包括教学示例图片生成、文本图层处理和数字水印技术实现方案。
|
5月前
|
存储 安全 Java
深入理解Java序列化接口及其实现机制
记住,序列化不仅仅是把对象状态保存下来那么简单,它涉及到类的版本控制、安全性和性能等多个重要方面。正确理解和实现Java序列化机制对于构建高效、安全和可维护的Java应用至关重要。
207 0
|
6月前
|
JSON Java 数据库连接