本文通过JMH(即Java Microbenchmark Harness,基于方法层面的基准测试,精度可以达到微秒级)来测试一下Java中几种常见的JSON解析库的性能。 每次都在网上看到别人说什么某某库性能是如何如何的好,碾压其他的库。但是百闻不如一见,只有自己亲手测试过的才是最值得相信的。
JSON不管是在Web开发还是服务器开发中是相当常见的数据传输格式,同时JSON是与开发语言无关,轻量级,一开始是JavaScript的,但是后面比较流传,几乎所有语言都有相应的使用API。
一般情况我们对于JSON解析构造的性能并不需要过于关心,除非是在性能要求比较高的系统。
目前对于Java开源的JSON类库有很多种,下面我们取4个常用的JSON库进行性能测试对比, 同时根据测试结果,开发者可以根据实际应用场景选择最合适的JSON库。
这4个JSON类库分别为:Gson,FastJson,Jackson,Json-lib。
简单介绍
选择一个合适的JSON库要从多个方面进行考虑:
- 字符串解析成JSON性能
- 字符串解析成JavaBean性能
- JavaBean构造JSON性能
- 集合构造JSON性能
- 易用性
先简单介绍下四个类库的身份背景
Gson
项目地址:https://github.com/google/gson
Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。 Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。 在使用这种对象转换之前,需先创建好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。 类里面只要有get和set方法,Gson完全可以实现复杂类型的json到bean或bean到json的转换,是JSON解析的神器。
FastJson
项目地址:https://github.com/alibaba/fastjson
Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。 FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。 FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。
Jackson
项目地址:https://github.com/FasterXML/jackson
Jackson是当前用的比较广泛的,用来序列化和反序列化json的Java开源框架。Jackson社区相对比较活跃,更新速度也比较快, 从Github中的统计来看,Jackson是最流行的json解析器之一,Spring MVC的默认json解析器便是Jackson。
Jackson优点很多:
- Jackson 所依赖的jar包较少,简单易用。
- 与其他 Java 的 json 的框架 Gson 等相比,Jackson 解析大的 json 文件速度比较快。
- Jackson 运行时占用内存比较低,性能比较好
- Jackson 有灵活的 API,可以很容易进行扩展和定制。
目前最新版本是2.9.9,Jackson 的核心模块由三部分组成:
- jackson-core 核心包,提供基于”流模式”解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
- jackson-annotations 注解包,提供标准注解功能;
- jackson-databind 数据绑定包,提供基于”对象绑定” 解析的相关 API( ObjectMapper )和”树模型” 解析的相关 API(JsonNode);基于”对象绑定” 解析的 API 和”树模型”解析的 API 和依赖基于”流模式”解析的 API。
Json-lib
项目地址:http://json-lib.sourceforge.net/index.html
json-lib最开始的也是应用最广泛的json解析工具,json-lib 不好的地方确实是依赖于很多第三方包,json-lib对于json转换成bean还有缺陷, 比如一个类里面会出现另一个类的list或者map集合,json-lib从json到bean的转换就会出现问题。json-lib在功能和性能上面都不能满足现在互联网化的需求。
编写性能测试
添加maven依赖
当然首先是添加四个库的maven依赖,公平起见,我全部使用它们最新的版本(作者当时的最新版本):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qrx</groupId>
<artifactId>jsontest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- json-lib -->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.9</version>
</dependency>
</dependencies>
</project>
四个库的工具类
FastJsonUtils.java
package JsonUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class FastJsonUtils {
private static final SerializerFeature[] features = {
// 序列化所有参数,包括null
SerializerFeature.WriteMapNullValue,
// 日期类型格式
SerializerFeature.WriteDateUseDateFormat
// list字段如果为null,输出为[],而不是null
// SerializerFeature.WriteNullListAsEmpty,
// 数值字段如果为null,输出为0,而不是null
// SerializerFeature.WriteNullNumberAsZero,
// Boolean字段如果为null,输出为false,而不是null
// SerializerFeature.WriteNullBooleanAsFalse,
// 字符类型字段如果为null,输出为"",而不是null
// SerializerFeature.WriteNullStringAsEmpty
};
public static String bean2Json(Object obj) {
return JSON.toJSONString(obj);
}
public static String bean2JsonFeatures(Object obj) {
return JSON.toJSONString(obj, features);
}
public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
return JSON.parseObject(jsonStr, objClass);
}
}
GsonUtils.java
package JsonUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
public class GsonUtils {
private static Gson gson = new GsonBuilder().create();
public static String bean2Json(Object obj) {
return gson.toJson(obj);
}
public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
return gson.fromJson(jsonStr, objClass);
}
public static String jsonFormatter(String uglyJsonStr) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(uglyJsonStr);
return gson.toJson(je);
}
}
JacksonUtils.java
package JsonUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
public class JacksonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
// 设置时区
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 日期类型格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 序列化所有参数,包括null
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
}
public static String bean2Json(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
try {
return objectMapper.readValue(jsonStr, objClass);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
JsonLibUtils.java
package JsonUtils;
import net.sf.json.JSONObject;
public class JsonLibUtils {
public static String bean2Json(Object obj) {
JSONObject jsonObject = JSONObject.fromObject(obj);
return jsonObject.toString();
}
@SuppressWarnings("unchecked")
public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
return (T) JSONObject.toBean(JSONObject.fromObject(jsonStr), objClass);
}
}
准备Model类
package model;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class Person {
private String name;
private int age;
private Date birthday;
private List<String> hobbies;
private Map<String, String> clothes;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Map<String, String> getClothes() {
return clothes;
}
public void setClothes(Map<String, String> clothes) {
this.clothes = clothes;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder("Person [name=" + name + ", age="
+ age + ", birthday=" + birthday + ", hobbies=" + hobbies
+ ", clothes=" + clothes + "]\n");
return str.toString();
}
}
JSON序列化性能测试
import JsonUtils.FastJsonUtils;
import JsonUtils.GsonUtils;
import JsonUtils.JacksonUtils;
import JsonUtils.JsonLibUtils;
import model.Person;
import java.util.*;
public class JsonSerializeTest {
private static final int count = 10000;
private Person p;
public static void main(String[] args) {
System.out.println("JsonSerializeTest");
JsonSerializeTest jst = new JsonSerializeTest();
jst.prepare();
long start;
long end;
start= System.currentTimeMillis();
jst.FastJson();
end = System.currentTimeMillis();
System.out.println("fastjson spend time:"+(end-start));
start = System.currentTimeMillis();
jst.Gson();
end = System.currentTimeMillis();
System.out.println("Gson spend time:"+(end-start));
start = System.currentTimeMillis();
jst.Jackson();
end = System.currentTimeMillis();
System.out.println("Jackson spend time:"+(end-start));
start = System.currentTimeMillis();
jst.JsonLib();
end = System.currentTimeMillis();
System.out.println("JsonLib spend time:"+(end-start));
}
public void JsonLib() {
for (int i = 0; i < count; i++) {
JsonLibUtils.bean2Json(p);
}
}
public void Gson() {
for (int i = 0; i < count; i++) {
GsonUtils.bean2Json(p);
}
}
public void FastJson() {
for (int i = 0; i < count; i++) {
FastJsonUtils.bean2Json(p);
}
}
public void Jackson() {
for (int i = 0; i < count; i++) {
JacksonUtils.bean2Json(p);
}
}
public void prepare() {
p=createAPerson("邵同学");
}
private Person createAPerson(String name) {
Person newPerson=new Person();
newPerson.setName(name);
newPerson.setAge(24);
List<String> hobbies=new ArrayList<>();
hobbies.add("篮球");
hobbies.add("游泳");
hobbies.add("coding");
newPerson.setHobbies(hobbies);
Map<String,String> clothes=new HashMap<>();
clothes.put("coat", "Nike");
clothes.put("trousers", "adidas");
clothes.put("shoes", "安踏");
newPerson.setClothes(clothes);
return newPerson;
}
}
输出:
JsonSerializeTest
fastjson spend time:728
Gson spend time:547
Jackson spend time:1283
JsonLib spend time:2190
JSON反序列化性能测试
import JsonUtils.FastJsonUtils;
import JsonUtils.GsonUtils;
import JsonUtils.JacksonUtils;
import JsonUtils.JsonLibUtils;
import model.Person;
public class JsonDeserializeTest {
/**
* 反序列化次数参数
*/
private static final int count = 10000;
private String jsonStr;
public static void main(String[] args) {
System.out.println("JsonDeserializeTest:");
JsonDeserializeTest jdst = new JsonDeserializeTest();
jdst.prepare();
long start;
long end;
start= System.currentTimeMillis();
jdst.FastJson();
end = System.currentTimeMillis();
System.out.println("fastjson spend time:"+(end-start));
start = System.currentTimeMillis();
jdst.Gson();
end = System.currentTimeMillis();
System.out.println("Gson spend time:"+(end-start));
start = System.currentTimeMillis();
jdst.Jackson();
end = System.currentTimeMillis();
System.out.println("Jackson spend time:"+(end-start));
start = System.currentTimeMillis();
jdst.JsonLib();
end = System.currentTimeMillis();
System.out.println("JsonLib spend time:"+(end-start));
}
public void JsonLib() {
for (int i = 0; i < count; i++) {
JsonLibUtils.json2Bean(jsonStr, Person.class);
}
}
public void Gson() {
for (int i = 0; i < count; i++) {
GsonUtils.json2Bean(jsonStr, Person.class);
}
}
public void FastJson() {
for (int i = 0; i < count; i++) {
FastJsonUtils.json2Bean(jsonStr, Person.class);
}
}
public void Jackson() {
for (int i = 0; i < count; i++) {
JacksonUtils.json2Bean(jsonStr, Person.class);
}
}
public void prepare() {
jsonStr = "{\"name\":\"邵同学\",\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"}}";
}
}
输出:
JsonDeserializeTest:
fastjson spend time:1281
Gson spend time:671
Jackson spend time:1280
JsonLib spend time:2546
序列化方法处理流程
序列化 :把对象转换为字节序列存储于磁盘或者进行网络传输的过程称为对象的序列化。
反序列化 :把磁盘或网络节点上的字节序列恢复到对象的过程称为对象的反序列化。
基本流程为:
- 首先,构建通用序列化基础方法所需要的参数类型对象;
- 其次,对序列化类型进行分析,根据注解或者”get方法名(比如getXxx,isXxx)”等来构建需要序列化的属性
- 然后,通过反射机制分别对所有的序列化属性进行处理:通过发现拿到对应的值,getxxx方法等
- 拼接字符串:其内部是根据类型写入一些开始结束符号,例如{,[等,在其中嵌入步骤3的解析设值
- 返回最后得到的字符串内容