性能大比拼!这三个主流的 JSON 解析库,一个快,一个稳,还有一个你想不到!...

阅读本文大概需要 5 分钟。

今天我们来聊聊 Java 中解析 JSON 的三个主流类库

FastJSONGson Jackson

先来看下这三货在 Github 上的基本数据:

FastJson

Github地址:https://github.com/alibaba/fastjson

Jackson

Github地址:https://github.com/FasterXML/jackson

Gson

Github地址:https://github.com/google/gson

码友们对这三货各方面也一直争议不断,大多关心的,还是性能以及稳定性。本文主测性能;对于这三个库的简介以及用法,就不去废话了,一个简单明了的测评,直接上!

主要对这三个类库在 JSON 序列化和反序列化在速度方面的表现做一些测评,为了防止由于内存导致测试结果出现偏差,测试中对 JVM 内存配置 -Xmx4g -Xms4g。

测试代码已经贴到了文章末尾。

JSON 序列化(Object => JSON)

测试样本数量为 100000 个,为了保证每个类库在测试中都能处理同一个样本,先把样本 Java 对象保存在文件中。每个类库测试 5 次,每次循环测试 10 遍,去掉最快速度和最慢速度,对剩下的 8 遍求平均值作为最终的速度,取 5 次测试中最好的平均速度作为最终的测试数据。

测试结果:

从测试数据可以看出,Jackson 是最快的(耗时比 Gson 少了大约 700 毫秒),Gson 耗时最久。

JSON 反序列化(JSON => Object)

同样,测试样本数量为 100000 个,为了保证每个类库在测试中都能处理同一个样本,先把样本 JSON 对象保存在文件中。每个类库测试 5 次,每次循环测试 10 遍,去掉最快速度和最慢速度,对剩下的 8 遍求平均值作为最终的速,取 5 次测试中最好的平均速度作为最终的测试数据。

测试结果:

从测试数据可以看出,在反序列化上性能 FastJson 最快,不过与 Jackson 差距并不明显,Gson 耗时最久

可见,不管是序列化还是反序列化,速度方面首先阵亡的,是 Gson!

那还剩下 FastJson 和 Jackson

FastJson 在某些方面确实快一些,但是和 Jackson 的差距不大,优势并没有太明显。Jackson 还可以加上 AfterBurner 来使用 byte generation,这样和 FastJson 的差距就更小了。

除了在反序列化的速度胜出外,FastJson 相比较 Jackson 有不少短板,我们可以从以下 3 方面对比:

1. 可定制性
Jackson 有灵活的 API,可以很容易进行扩展和定制,而且很多时候需要的模块都已经有人提供了。比如 guava 中定义的数据类型,比如 kotlin 语言 Immutable 的类型等,比如 java8 引入的新日期时间类型和 Optional 都已经有支持的模块。

FastJson 只有一个(简陋)的 SerializeFilter 机制用来定制序列化,ParseProcess 机制用来定制反序列化,每次调用序列化/反序列化的的时候都要自己传 filter 或者 Process 这个参数过去,Jackson 和 Gson 都是直接注册模块就可以了,Jackson 还可以使用 SPI 来自动发现和注册模块。

2. 代码质量
公司有一些项目使用了 Fastjson,在使用 Fastjson 的项目里面曾碰到过的两个低级 bug:

1. 碰到在 128~255 的字符直接异常,这些主要是西欧语言的字符,因为他用一个数组来记录 转义后的字符表示,但是数组长度只有 128...

2. 内存占用过多。Fastjson 为了性能,在 ThreadLocal 中缓存了 char[] buffer,这样避免分配内存和 gc 的开销。但是如果碰到了大的 json(比如 10M 这样的),就会占用大量的内存,而且以后都是处理小 JSON 了内存占用也回不来。
这些问题虽然后来的版本都修复了,但是也反映出 Fastjson  代码质量上要求不够严格。而 Jackson 这么多年来使用上还没有碰到过这样的 Bug.

3. 文档
相比 Jackson, Fastjson 英文文档就显得比较欠缺,已有的也不规范,这样就更没法指望老外用了,相对还是国内开发者占大多数

这么来看,最终结果很明显了:

观点

JSON 从发明到现在之所以流行,并不是因为 json 快的原因(比 json 快且小巧的格式和类库一大把),而是因为 json 和 web 结合的时候更易于使用,对开发人员易于理解。很多人拿 FastJson 和 Jackson 比,就像拿非智能机和 iphone 比待机时间,其功能性不一样,Jackson 的很多功能 FastJson 并没有实现,所以这种对比也不客观。FastJson 之所以没在国际上流行起来,最主要的原因应该是开发者的思路全放到“快”上去了,而偏离了“标准”及功能性,质量也不够好,有点“舍本逐末”的味道。

当然在目前的环境下,国产软件能踏实的心态做好开源的不多,FastJson 团队能这么快的反馈并修正问题,这种精神还是值得称赞的。希望国内的技术从业者能更重视“技术的原始需求”。

测试代码:

样本对象包括 Boolean、Int、Long、Double、Date、String、List 和 Map 字段,其中 List 长度和 Map 的 Key 数量可以根据需要改变。

/**
 * 样本数据工厂 提供各类元数据样本
 */
public class DataFactory {
    private static final String[] chars = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b",
            "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w",
            "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z" };


    // 配置各项长度/数量
    private static final int charNum = 62;
    private static final int maxStrLength = 120;
    private static final int defaultStrLength = 50;
    private static final int maxListSize = 120;
    private static final int defaultListSize = 20;
    private static final int maxMapSize = 120;
    private static final int defaultMapSize = 20;
    private static final String[] types = new String[] { "string", "int", "long", "double", "boolean", "date"};
    private static final int typeNum = 6;
    private static final Random random = new Random();
    /**
     * 生成随机长度的字符串
     * @return 字符串
     */
    public static String randomString(){
        return randomString(random.nextInt(maxStrLength));
    }
    /**
     * 生成指定长度字符串
     * @param len 字符串长度
     */
    public static String randomString(int len) {
        if (len < 1 || len > maxStrLength) {
            len = defaultStrLength;
        }
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            sb.append(chars[random.nextInt(charNum)]);
        }
        return sb.toString();
    }
    /**
     * 生成List,元素的数量随机
     * @return
     */
    public static List<String> randomStringList() {
        return randomStringList(random.nextInt(maxListSize));
    }
    /**
     * 生成List样本
     * @param size 元素的数量 
     * @return
     */
    public static List<String> randomStringList(int size) {
        if (size &lt; 1 || size &gt; maxListSize) {
            size = defaultListSize;
        }
        List<String> list = new ArrayList<String>();
        for (int i = 0; i &lt; size; i++) {
            list.add(randomString(random.nextInt(maxStrLength)));
        }
        return list;
    }
    /**
     * 生成随机Map样本,key的数量随机
     * @return
     */
    public static Map<String, Object> randomMap() {
        return randomMap(random.nextInt(maxMapSize));
    }
    /**
     * 生成随机Map样本
     * @param size key的数量
     * @return
     */
    public static Map<String, Object> randomMap(int size) {
        if (size < 1 || size > maxMapSize) {
            size = defaultMapSize;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < size; i++) {
            String type = types[random.nextInt(typeNum)];
            if ("boolean".equals(type)) {
                map.put("key" + i, random.nextBoolean());
            } else if ("int".equals(type)) {
                map.put("key" + i, random.nextInt());
            } else if ("long".equals(type)) {
                map.put("key" + i, random.nextLong());
            } else if ("double".equals(type)) {
                map.put("key" + i, random.nextDouble());
            } else if ("date".equals(type)) {
                map.put("key" + i, new Date());
            } else if ("string".equals(type)) {
                map.put("key" + i, randomString(random.nextInt(maxStrLength)));
            }
        }
        return map;
    }
}
/**
 * 样本对象
 */
public class TestEntity implements Serializable {
    private Double dataDouble;
    private Date dataDate;
    private String dataStr;
    private Boolean dataBoolean;
    private Integer dataInt;
    private Long dataLong;
    private List<String> dataList;
    private Map<String, Object> dataMap;


    public TestEntity() {
        Random random = new Random();
        dataBoolean = random.nextBoolean();
        dataInt = random.nextInt();
        dataLong = random.nextLong();
        dataDouble = random.nextDouble();
        dataDate = new Date();
        dataStr = DataFactory.randomString();
        dataList = DataFactory.randomStringList(defaultListSize);
        dataMap = DataFactory.randomMap(mapKeyNum);
    }
    /**
     * 指定元素数量的样本
     */
    public TestEntity(int listSize, int mapKeyNum) {
        Random random = new Random();
        dataBoolean = random.nextBoolean();
        dataInt = random.nextInt();
        dataLong = random.nextLong();
        dataDouble = random.nextDouble();
        dataDate = new Date();
        dataStr = DataFactory.randomString();
        dataList = DataFactory.randomStringList(listSize);
        dataMap = DataFactory.randomMap(defaultMapSize);
    }


    // get and set
    // ......
}
/**
 * 测试入口
 */
public class TestBuilder {
    public static void main(String[] args) {
        int testSize = 100000;
        String jsonDataPath = "d:\\test_json.dat";
        String objectDataPath = "d:\\test_object.dat";
        buildJson(testSize, 10, 10, jsonDataPath);
        buildObject(testSize, 10, 10, objectDataPath);
    }
    public static List<String> loadJSON(String filePath) {
        List<String> list = new LinkedList<String>();
        File file = new File(filePath);
        if (!file.exists()) {
            return list;
        }
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file));
            String line = br.readLine();            
            while(line != null){
                list.add(line);
                line = br.readLine();
            }           
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
        }
        return list;
    }
    @SuppressWarnings("unchecked")
    public static List<TestEntity> loadTests(String filePath) {
        List<TestEntity> list = new LinkedList<TestEntity>();
        File file = new File(filePath);
        if (!file.exists()) {
            return list;
        }
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            list = (List<TestEntity>) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != ois) {
                try {
                    ois.close();
                } catch (IOException e) {
                }
            }
        }
        return list;
    }
    /**
     * 创建样本
     * 
     * @param testSize 样本数量
     * @param listSize 样本List长度
     * @param mapKeyNum 样本Map的Key数量
     * @return 样本List
     */
    public static List<TestEntity> buildTest(int testSize, int listSize, int mapKeyNum) {
        List<TestEntity> list = new LinkedList<TestEntity>();
        for (int i = 0; i < testSize; i++) {
            list.add(new TestEntity(listSize, mapKeyNum));
        }
        return list;
    }
    /**
     * 创建默认样本
     */
    public static List<TestEntity> buildTest(int testSize) {
        List<TestEntity> list = new LinkedList<TestEntity>();
        for (int i = 0; i < testSize; i++) {
            list.add(new TestEntity());
        }
        return list;
    }
    /**
     * 创建样本,并把样本JSON序列化,保存到文件中。
     * 
     * @param testSize 样本数量
     * @param listSize 样本List长度
     * @param mapKeyNum 样本Map中Key的数量
     * @param filePath 样本输出的文件路径
     */
    public static void buildJson(int testSize, int listSize, int mapKeyNum, String filePath) {
        File file = new File(filePath);
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        if (file.exists()) {
            file.delete();
        }
        List<TestEntity> list = buildTest(testSize, listSize, mapKeyNum);
        StringBuilder sb = new StringBuilder();
        for (TestEntity item : list) {
            sb.append(JSON.toJSONString(item));
            sb.append("\n");
        }
        BufferedWriter bw = null;
        try {
            file.createNewFile();


            bw = new BufferedWriter(new FileWriter(file));
            bw.write(sb.toString());
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != bw) {
                try {
                    bw.close();
                } catch (IOException e) {
                }
            }
        }
    }
    public static void buildJson(int testSize, String filePath) {
        File file = new File(filePath);
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        if (file.exists()) {
            file.delete();
        }
        List<TestEntity> list = buildTest(testSize);
        StringBuilder sb = new StringBuilder();
        for (TestEntity item : list) {
            sb.append(JSON.toJSONString(item));
            sb.append("\n");
        }
        BufferedWriter bw = null;
        try {
            file.createNewFile();
            bw = new BufferedWriter(new FileWriter(file));
            bw.write(sb.toString());
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != bw) {
                try {
                    bw.close();
                } catch (IOException e) {
                }TestEntity
            }
        }
    }
    public static void buildObject(int testSize, String filePath) {
        List<TestEntity>  list = buildTest(testSize);
        File file = new File(filePath);
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        if (file.exists()) {
            file.delete();
        }
        ObjectOutputStream oos = null;
        try {
            file.createNewFile();
            oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(list);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {
                }
            }
        }
    }
    /**
     * 生成样本对象,并保存到指定文件
     * 
     * @param testSize 样本大小
     * @param listSize 样本中List字段长度
     * @param mapKeyNum 样本中Map对象Key数量
     * @param filePath 样本输出的路径
     */
    public static void buildObject(int testSize, int listSize, int mapKeyNum, String filePath) {
        List<TestEntity> list = buildTest(testSize, listSize, mapKeyNum);
        File file = new File(filePath);
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        if (file.exists()) {
            file.delete();
        }
        ObjectOutputStream oos = null;
        try {
            file.createNewFile();
            oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(list);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

好啦,以上呢,就是这期给大家的分享啦,如果您有什么想看的主题,可以在文内留言哦~

推荐阅读

1

干掉垃圾广告!自从装了这个神器,看博客舒服到停不下来!超清爽!

2

有个程序员老公有多爽???

3

太强了!GitHub 中文开源项目榜单出炉,暴露了程序员的硬性需求!

4‍‍

漂亮!竟然用一个脚本就把系统升级到https了,且永久免费!

崔庆才

静觅博客博主,《Python3网络爬虫开发实战》作者

隐形字

个人公众号:进击的Coder

长按识别二维码关注

好文和朋友一起看~

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值