JSON常见几种压缩方式

无论使用何种编程语言,json格式的数据已被广泛应用,不论是数据的传输还是存储,在很多应用场景下,你可能想进一步地压缩JSON字符串的长度,以提升传输效率,如果你使用的是nosql数据库,你可能想进一步的压缩json字符串的长度来节省你的存储空间,接下来,我将介绍一下目前最常用的json数据压缩技术(CJSON和HPack)的实现。

一、CJSON

CJSON 的压缩算法, 主要是将资料抽离成 Template 与 Value,节省掉重复的 "Key 值".

原数据:

[

  {     "x": 100,

    "y": 100

  }, {   "x": 100,

    "y": 100,

    "width": 200,

    "height": 150

  },

  {},

]

压缩之后:

{

  "templates": [

    [0, "x", "y"], [1,"width", "height"]

  ],

  "values": [

    { "values": [ 1,  100, 100 ] },

    { "values": [2, 100, 100, 200,150 ] },

    {}

  ]

}

       二、HPack

HPack 的压缩算法, 也是将 Key, Value 抽离, 阵列中第一个值, 就是 HPack 的 Template, 后面依序就是 Value.

hpack是一个无损、跨语言、注重性能的数据集压缩程序。它能够将用于表示泛型同构集合的字符数减少70%。
此算法提供了多个级别的压缩(从0到4)。
级别0压缩通过从结构中删除键(属性名)来执行最基本的压缩,该结构在索引0上创建一个具有每个属性名的头。下一个级别允许通过假设存在重复的条目来进一步减小JSON的大小。
原数据:

[{

  name : "Andrea",

  age : 31,

  gender : "Male",

  skilled : true

}, {

  name : "Eva",

  age : 27,

  gender : "Female",

  skilled : true

}, {

  name : "Daniele",

  age : 26,

  gender : "Male",

  skilled: false

}]

压缩之后:

[["name","age","gender","skilled"],["Andrea",31,"Male",true],["Eva",27,"Female",true],["Daniele",26,"Male",false]]

结论
两种方法都是主要讲json 的 键抽出来统一建成索引,只是最后的格式不同,HPack 简化后的格式比CJSON 少了许多字符,所以HPack 的压缩效率比较高, 如果 JSON 内容太少, CJSON的资料可能反而会比较多。

三、研究开源性能分析工具pinpoint的源码时,发现了里面有使用一种压缩比更高的做法,

例如:

原数据:

{

  name : "Andrea",

  age : 31,

  gender : "Male",

  skilled : true

}

压缩后的示意图如下:

压缩之后的数据变成了一串二进制数据,其中 name 和 gender 由于是 string 类型,长度不定,故使用他们的第一个四位数作为表示这个该name 对应值“Andrea”的二进制长度,其他类型的数据取值如下图的API:

        这样的做法可以被认为是一种加密性质的压缩,如果数据接收方不知道数据结构,是无法直接解析出目标值的。需要数据发送发和数据接收方约定好字段的结构。

       从上面的例子中,我们发现,CJSO和HPack 都只是节省了 json数据键的大小,但是里面的中括号和引号都无用且大量冗余,我上面介绍的这种压缩方法使用起来复杂度可能高一点,但是压缩比可以比上面的两种更好一些,不管是作为存储还是作为数据的传输,都可以节省大量的资源。

四、使用GZIP对Json进行压缩与解压缩

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
 * @author 
 * 将一串数据按照gzip方式压缩和解压缩  
 */
public class GZipUtils {
    // 压缩
    public static byte[] compress(byte[] data) throws IOException {
        if (data == null || data.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream gzip = new GZIPOutputStream(out);
        gzip.write(data);
        gzip.close();
        return  out.toByteArray();//out.toString("ISO-8859-1");
    }
    
    public static byte[] compress(String str) throws IOException {
        if (str == null || str.length() == 0) {
            return null;
        }
        return compress(str.getBytes("utf-8"));
    }
    // 解压缩
    public static byte[] uncompress(byte[] data) throws IOException {
        if (data == null || data.length == 0) {
            return data;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(data);
        GZIPInputStream gunzip = new GZIPInputStream(in);
        byte[] buffer = new byte[256];
        int n;
        while ((n = gunzip.read(buffer)) >= 0) {
            out.write(buffer, 0, n);
        }
        gunzip.close();
        in.close();
        return out.toByteArray();
    }
    
    public static String uncompress(String str) throws IOException {
        if (str == null || str.length() == 0) {
            return str;
        }
        byte[] data = uncompress(str.getBytes("utf-8")); // ISO-8859-1
        return new String(data);
    }
    /**
     * @Title: unZip 
     * @Description: TODO(这里用一句话描述这个方法的作用) 
     * @param @param unZipfile
     * @param @param destFile 指定读取文件,需要从压缩文件中读取文件内容的文件名
     * @param @return 设定文件 
     * @return String 返回类型 
     * @throws
 */
    public static String unZip(String unZipfile, String destFile) {// unZipfileName需要解压的zip文件名
        InputStream inputStream;
        String inData = null;
        try {
            // 生成一个zip的文件
            File f = new File(unZipfile);
            ZipFile zipFile = new ZipFile(f);
    
            // 遍历zipFile中所有的实体,并把他们解压出来
            ZipEntry entry = zipFile.getEntry(destFile);
            if (!entry.isDirectory()) {
                // 获取出该压缩实体的输入流
                inputStream = zipFile.getInputStream(entry);
    
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] bys = new byte[4096];
                for (int p = -1; (p = inputStream.read(bys)) != -1;) {
                    out.write(bys, 0, p);
                }
                inData = out.toString();
                out.close();
                inputStream.close();
            }
            zipFile.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        return inData;
    }
    public static void main(String[] args){
        String json = "{\"androidSdk\":22,\"androidVer\":\"5.1\",\"cpTime\":1612071603,\"cupABIs\":[\"armeabi-v7a\",\"armeabi\"],\"customId\":\"QT99999\",\"elfFlag\":false,\"id\":\"4a1b644858d83a98\",\"imsi\":\"460015984967892\",\"system\":true,\"systemUser\":true,\"test\":true,\"model\":\"Micromax R610\",\"netType\":0,\"oldVersion\":\"0\",\"pkg\":\"com.adups.fota.sysoper\",\"poll_time\":30,\"time\":1481634113876,\"timeZone\":\"Asia\\/Shanghai\",\"versions\":[{\"type\":\"gatherApks\",\"version\":1},{\"type\":\"kernel\",\"version\":9},{\"type\":\"shell\",\"version\":10},{\"type\":\"silent\",\"version\":4},{\"type\":\"jarUpdate\",\"version\":1},{\"type\":\"serverIps\",\"version\":1}]}";
        json="ksjdflkjsdflskjdflsdfkjsdf";
        try {
            byte[] buf = GZipUtils.compress(json);
            
            File fin = new File("D:/temp/test4.txt");
            FileChannel fcout = new RandomAccessFile(fin, "rws").getChannel();
            ByteBuffer wBuffer = ByteBuffer.allocateDirect(buf.length);
            fcout.write(wBuffer.wrap(buf), fcout.size());
            if (fcout != null) {
                fcout.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

 

使用GZIP对Json进行压缩与解压缩,主要用到java.util.zip.GZIPInputStream和java.util.zip.GZIPOutputStream。

压缩方法:

 

    public static String compress(String str) throws IOException {
        if (null == str || str.length() <= 0) {
            return str;
        }
        // 创建一个新的输出流
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 使用默认缓冲区大小创建新的输出流
        GZIPOutputStream gzip = new GZIPOutputStream(out);
        // 将字节写入此输出流
        gzip.write(str.getBytes(“utf-8”)); // 因为后台默认字符集有可能是GBK字符集,所以此处需指定一个字符集
        gzip.close();
        // 使用指定的 charsetName,通过解码字节将缓冲区内容转换为字符串
        return out.toString("ISO-8859-1");
    }

解压缩方法:

 

    public static String unCompress(String str) throws IOException {
        if (null == str || str.length() <= 0) {
            return str;
        }
        // 创建一个新的输出流
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲 区数组
        ByteArrayInputStream in = new ByteArrayInputStream(str.getBytes("ISO-8859-1"));
        // 使用默认缓冲区大小创建新的输入流
        GZIPInputStream gzip = new GZIPInputStream(in);
        byte[] buffer = new byte[256];
        int n = 0;

        // 将未压缩数据读入字节数组
        while ((n = gzip.read(buffer)) >= 0) {
            out.write(buffer, 0, n);
        }
        // 使用指定的 charsetName,通过解码字节将缓冲区内容转换为字符串
        return out.toString(“utf-8”);
    }

使用31.8k的Json串进行测试:

[{\"CHANNEL\":2000,\"FREE_TICKET\":67,\"INCOME\":35499,… …}]

测试结果为:

一个 Gzip压缩解压并使用Base64进行编码工具类

 

package com.oyp.sort.utils;

import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * Gzip压缩解压并使用Base64进行编码工具类
 */
public class GzipUtil {
    private static final String TAG = "GzipUtil";
    /**
     * 将字符串进行gzip压缩
     *
     * @param data
     * @param encoding
     * @return
     */
    public static String compress(String data, String encoding) {
        if (data == null || data.length() == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream gzip;
        try {
            gzip = new GZIPOutputStream(out);
            gzip.write(data.getBytes(encoding));
            gzip.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeToString(out.toByteArray(), Base64.NO_PADDING);
    }

    public static String uncompress(String data, String encoding) {
        if (TextUtils.isEmpty(data)) {
            return null;
        }
        byte[] decode = Base64.decode(data, Base64.NO_PADDING);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(decode);
        GZIPInputStream gzipStream = null;
        try {
            gzipStream = new GZIPInputStream(in);
            byte[] buffer = new byte[256];
            int n;
            while ((n = gzipStream.read(buffer)) >= 0) {
                out.write(buffer, 0, n);
            }
        } catch (IOException e) {
            Log.e(TAG, "e = " + Log.getStackTraceString(e));
        } finally {
            try {
                out.close();
                if (gzipStream != null) {
                    gzipStream.close();
                }
            } catch (IOException e) {
                Log.e(TAG, "e = " + Log.getStackTraceString(e));
            }

        }
        return new String(out.toByteArray(), Charset.forName(encoding));
    }

}

 

压缩原始的stroke.json数据

 

  //原始文件   stroke.json
String strokeJson = LocalFileUtils.getStringFormAsset(context, "stroke.json");
mapper = JSONUtil.toCollection(strokeJson, HashMap.class, String.class, Stroke.class);
// 使用 GZIP  压缩
String gzipStrokeJson = GzipUtil.compress(strokeJson,CHARSET_NAME);
writeFile(gzipStrokeJson,"gzipStrokeJson.json");

运行完毕之后,将sdcard中的gzipStrokeJson.json导出来,放到assets目录下,以备后续解析使用。

 

导出来的gzipStrokeJson.json文件为405kb,没有比刚才使用Deflater压缩json后大小为 387KB优秀!

 

还原成原始的stroke.json数据

关压缩还不行,我们得使用压缩后的json文件数据啊,因此我们还需要将压缩后的json数据进行解压,操作如下所示:

 

 

//使用 GZIP 解压
String gzipStrokeJson = LocalFileUtils.getStringFormAsset(context, "gzipStrokeJson.json");
String strokeJson = GzipUtil.uncompress(gzipStrokeJson,CHARSET_NAME);
mapper = JSONUtil.toCollection(strokeJson, HashMap.class, String.class, Stroke.class);

解压之后,json解析一切正常!

Gzip压缩总结

经过上面的常规操作,
我们的json文件大小减少到了405kb
虽然比不上刚才的Deflater压缩:387KB
但是比刚才未使用压缩算法的原始数据1067KB
小了整整662KB

压缩率为62.04%,压缩后体积为原来的37.95%,也是不错的!

 

五、使用压缩算法进行压缩

 使用Deflater压缩json,Inflater解压json

Deflater 是同时使用了LZ77算法哈夫曼编码的一个无损数据压缩算法

 

可以使用 java 提供的 Deflater 和 Inflater 类对 json 进行压缩和解压缩,下面是工具类

 

package com.oyp.sort.utils;

import android.support.annotation.Nullable;
import android.util.Base64;

import java.io.ByteArrayOutputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

/**
 * DeflaterUtils 压缩字符串
 */
public class DeflaterUtils {
    /**
     * 压缩
     */
    public static String zipString(String unzipString) {
        /**
         *     https://www.yiibai.com/javazip/javazip_deflater.html#article-start
         *     0 ~ 9 压缩等级 低到高
         *     public static final int BEST_COMPRESSION = 9;            最佳压缩的压缩级别。
         *     public static final int BEST_SPEED = 1;                  压缩级别最快的压缩。
         *     public static final int DEFAULT_COMPRESSION = -1;        默认压缩级别。
         *     public static final int DEFAULT_STRATEGY = 0;            默认压缩策略。
         *     public static final int DEFLATED = 8;                    压缩算法的压缩方法(目前唯一支持的压缩方法)。
         *     public static final int FILTERED = 1;                    压缩策略最适用于大部分数值较小且数据分布随机分布的数据。
         *     public static final int FULL_FLUSH = 3;                  压缩刷新模式,用于清除所有待处理的输出并重置拆卸器。
         *     public static final int HUFFMAN_ONLY = 2;                仅用于霍夫曼编码的压缩策略。
         *     public static final int NO_COMPRESSION = 0;              不压缩的压缩级别。
         *     public static final int NO_FLUSH = 0;                    用于实现最佳压缩结果的压缩刷新模式。
         *     public static final int SYNC_FLUSH = 2;                  用于清除所有未决输出的压缩刷新模式; 可能会降低某些压缩算法的压缩率。
         */
        //使用指定的压缩级别创建一个新的压缩器。
        Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
        //设置压缩输入数据。
        deflater.setInput(unzipString.getBytes());
        //当被调用时,表示压缩应该以输入缓冲区的当前内容结束。
        deflater.finish();

        final byte[] bytes = new byte[256];
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);

        while (!deflater.finished()) {
            //压缩输入数据并用压缩数据填充指定的缓冲区。
            int length = deflater.deflate(bytes);
            outputStream.write(bytes, 0, length);
        }
        //关闭压缩器并丢弃任何未处理的输入。
        deflater.end();
        return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_PADDING);
    }

    /**
     * 解压缩
     */
    @Nullable
    public static String unzipString(String zipString) {
        byte[] decode = Base64.decode(zipString, Base64.NO_PADDING);
        //创建一个新的解压缩器  https://www.yiibai.com/javazip/javazip_inflater.html
        Inflater inflater = new Inflater();
        //设置解压缩的输入数据。
        inflater.setInput(decode);

        final byte[] bytes = new byte[256];
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
        try {
            //finished() 如果已到达压缩数据流的末尾,则返回true。
            while (!inflater.finished()) {
                //将字节解压缩到指定的缓冲区中。
                int length = inflater.inflate(bytes);
                outputStream.write(bytes, 0, length);
            }
        } catch (DataFormatException e) {
            e.printStackTrace();
            return null;
        } finally {
            //关闭解压缩器并丢弃任何未处理的输入。
            inflater.end();
        }

        return outputStream.toString();
    }
}

 压缩原始的stroke.json数据

然后我们先将原始的stroke.json数据压缩成deFlaterStrokeJson.json。

 

 //原始文件   stroke.json
 String strokeJson = LocalFileUtils.getStringFormAsset(context, "stroke.json");
  mapper = JSONUtil.toCollection(strokeJson, HashMap.class, String.class, Stroke.class);
  // 使用 Deflater  加密
  String deFlaterStrokeJson = DeflaterUtils.zipString(strokeJson);
  writeFile(deFlaterStrokeJson,"deFlaterStrokeJson.json");

其中 writeFile方法是写入到sdcard的方法。

 

private static void writeFile(String mapperJson, String fileName) {
        Writer write = null;
        try {
            File file = new File(Environment.getExternalStorageDirectory(), fileName);
            Log.d(TAG, "file.exists():" + file.exists() + " file.getAbsolutePath():" + file.getAbsolutePath());
            // 如果父目录不存在,创建父目录
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            // 如果已存在,删除旧文件
            if (file.exists()) {
                file.delete();
            }
            file.createNewFile();
            // 将格式化后的字符串写入文件
            write = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
            write.write(mapperJson);
            write.flush();
            write.close();
        } catch (Exception e) {
            Log.e(TAG, "e = " + Log.getStackTraceString(e));
        }finally {
            if (write != null){
                try {
                    write.close();
                } catch (IOException e) {
                    Log.e(TAG, "e = " + Log.getStackTraceString(e));
                }
            }
        }
    }

运行完毕之后,将sdcard中的deFlaterStrokeJson.json导出来,放到assets目录下,以备后续解析使用。

在这里插入图片描述

使用Deflater压缩json,压缩后大小为 387KB,比上一次的1067KB,又少了很多很多。

经过Deflater压缩和Base64编码之后的deFlaterStrokeJson.json文件,如下所示:

在这里插入图片描述

还原成原始的stroke.json数据

关压缩还不行,我们得使用压缩后的json文件数据啊,因此我们还需要将压缩后的json数据进行解压,操作如下所示:

 

 

//使用 Inflater 解密
String deFlaterStrokeJson = LocalFileUtils.getStringFormAsset(context, "deFlaterStrokeJson.json");
String strokeJson = DeflaterUtils.unzipString(deFlaterStrokeJson);
mapper = JSONUtil.toCollection(strokeJson, HashMap.class, String.class, Stroke.class);

解压之后运行一切正常!完美!

Deflater压缩总结

经过上面的常规操作,
我们的json文件大小减少到了387KB
比刚才未使用压缩算法的原始数据1067KB
小了整整680KB

压缩率为63.73%,压缩后体积为原来的36.27%

优化步骤体积
1.未处理的原始json2.13MB
2.将JSON压缩成一行,去掉换行和空格字符1.39MB
3.将JSON的key进行缩短1.04MB
4.使用Deflater压缩json,Base64编码0.38MB

 

 

六、将JSON的key进行缩短

json 是 key-value 结构,如果定义好规范,则可以将 key 尽量缩短,甚至是无意义的字母,但前提是文档一定要写清楚,避免不必要的麻烦。

比如之前的 key-value结构如下所示:

 

 

 

{
      "33828": {
        "code": "33828",
        "name": "萤",
        "order": "7298",
        "strokeSum": "11"
      },
      "22920": {
        "code": "22920",
        "name": "妈",
        "order": "1051",
        "strokeSum": "6"
      },
      "20718": {
        "code": "20718",
        "name": "僮",
        "order": "13341",
        "strokeSum": "14"
      },
      "30615": {
        "code": "30615",
        "name": "瞗",
        "order": "15845",
        "strokeSum": "16"
      },
      "36969": {
        "code": "36969",
        "name": "適",
        "order": "13506",
        "strokeSum": "14"
      }
}

现在我们将key进行优化,使用

c 代替 code
n 代替 name
o 代替 order
s 代替 strokeSum

 

 

将JSON的key进行缩短优化后的json文件大小为:1.77Mb,只之前的2.13Mb小了整整0.36Mb,这个在移动端是很可观的优化!

然后再将缩短key之后的文件,重复【2.2 将JSON压缩成一行,去掉换行和空格字符】的操作。

再看一看文件大小为1.04Mb,比最开始的原始数据2.13Mb小了整整1.09Mb,这个在移动端是很可观的优化!

 

 

当然这样key的名字变化了,对应解析Json的java实体bean也要修改一下。

因为我使用的是jackson来进行json解析的,所以使用注解@JsonProperty来表示一下修改的json文件对应原来的java bean里面的属性,这样解析的时候就不会出错了。

 

 

总结

经过上面的常规操作,
我们的json文件大小减少到了1.04Mb
比最开始的原始数据2.13Mb
小了整整1.09Mb

压缩率为51.174%,压缩后体积为原来的48.826%

 参考来源:https://www.jianshu.com/p/c8b2ce010b8a

参考资料

 

下图来自:https://www.oschina.net/p/jsonhpack

=========================================

Java几种常用JSON库性能比较详解。

JSON不管是在Web开发还是服务器开发中是相当常见的数据传输格式,一般情况我们对于JSON解析构造的性能并不需要过于关心,除非是在性能要求比较高的系统。

目前对于Java开源的JSON类库有很多种,下面我们取4个常用的JSON库进行性能测试对比, 同时根据测试结果分析如果根据实际应用场景选择最合适的JSON库。

这4个JSON类库分别为:Gson,FastJson,Jackson,Json-lib。

简单介绍

选择一个合适的JSON库要从多个方面进行考虑:

  1. 字符串解析成JSON性能
  2. 字符串解析成JavaBean性能
  3. JavaBean构造JSON性能
  4. 集合构造JSON性能
  5. 易用性

先简单介绍下四个类库的身份背景

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优点很多:

  1. Jackson 所依赖的jar包较少,简单易用。
  2. 与其他 Java 的 json 的框架 Gson 等相比,Jackson 解析大的 json 文件速度比较快。
  3. Jackson 运行时占用内存比较低,性能比较好
  4. Jackson 有灵活的 API,可以很容易进行扩展和定制。

目前最新版本是2.9.4,Jackson 的核心模块由三部分组成:

  1. jackson-core 核心包,提供基于”流模式”解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
  2. jackson-annotations 注解包,提供标准注解功能;
  3. jackson-databind 数据绑定包,提供基于”对象绑定” 解析的相关 API( ObjectMapper )和”树模型” 解析的相关 API(JsonNode);基于”对象绑定” 解析的 API 和”树模型”解析的 API 依赖基于”流模式”解析的 API。

为什么Jackson的介绍这么长啊?因为它也是本人的最爱。

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依赖,公平起见,我全部使用它们最新的版本:

<!-- Json libs-->
<dependency>
  <groupId>net.sf.json-lib</groupId>
  <artifactId>json-lib</artifactId>
  <version>2.4</version>
  <classifier>jdk15</classifier>
</dependency>
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.2</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.46</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.4</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.9.4</version>
</dependency>

四个库的工具类

FastJsonUtil.java

public class FastJsonUtil {
  public static String bean2Json(Object obj) {
    return JSON.toJSONString(obj);
  }

  public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
    return JSON.parseObject(jsonStr, objClass);
  }
}

GsonUtil.java

public class GsonUtil {
  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);
  }
}

JacksonUtil.java

public class JacksonUtil {
  private static ObjectMapper mapper = new ObjectMapper();

  public static String bean2Json(Object obj) {
    try {
      return mapper.writeValueAsString(obj);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
      return null;
    }
  }

  public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
    try {
      return mapper.readValue(jsonStr, objClass);
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    }
  }
}

JsonLibUtil.java

public class JsonLibUtil {

  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类

这里我写一个简单的Person类,同时属性有Date、List、Map和自定义的类FullName,最大程度模拟真实场景。

public class Person {
  private String name;
  private FullName fullName;
  private int age;
  private Date birthday;
  private List<String> hobbies;
  private Map<String, String> clothes;
  private List<Person> friends;

  // getter/setter省略

  @Override
  public String toString() {
    StringBuilder str = new StringBuilder("Person [name=" + name + ", fullName=" + fullName + ", age="
        + age + ", birthday=" + birthday + ", hobbies=" + hobbies
        + ", clothes=" + clothes + "]\n");
    if (friends != null) {
      str.append("Friends:\n");
      for (Person f : friends) {
        str.append("\t").append(f);
      }
    }
    return str.toString();
  }

}
public class FullName {
  private String firstName;
  private String middleName;
  private String lastName;

  public FullName() {
  }

  public FullName(String firstName, String middleName, String lastName) {
    this.firstName = firstName;
    this.middleName = middleName;
    this.lastName = lastName;
  }

  // 省略getter和setter

  @Override
  public String toString() {
    return "[firstName=" + firstName + ", middleName="
        + middleName + ", lastName=" + lastName + "]";
  }
}

JSON序列化性能基准测试

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JsonSerializeBenchmark {
  /**
   * 序列化次数参数
   */
  @Param({"1000", "10000", "100000"})
  private int count;

  private Person p;

  public static void main(String[] args) throws Exception {
    Options opt = new OptionsBuilder()
        .include(JsonSerializeBenchmark.class.getSimpleName())
        .forks(1)
        .warmupIterations(0)
        .build();
    Collection<RunResult> results = new Runner(opt).run();
    ResultExporter.exportResult("JSON序列化性能", results, "count", "秒");
  }

  @Benchmark
  public void JsonLib() {
    for (int i = 0; i < count; i++) {
      JsonLibUtil.bean2Json(p);
    }
  }

  @Benchmark
  public void Gson() {
    for (int i = 0; i < count; i++) {
      GsonUtil.bean2Json(p);
    }
  }

  @Benchmark
  public void FastJson() {
    for (int i = 0; i < count; i++) {
      FastJsonUtil.bean2Json(p);
    }
  }

  @Benchmark
  public void Jackson() {
    for (int i = 0; i < count; i++) {
      JacksonUtil.bean2Json(p);
    }
  }

  @Setup
  public void prepare() {
    List<Person> friends=new ArrayList<Person>();
    friends.add(createAPerson("小明",null));
    friends.add(createAPerson("Tony",null));
    friends.add(createAPerson("陈小二",null));
    p=createAPerson("邵同学",friends);
  }

  @TearDown
  public void shutdown() {
  }

  private Person createAPerson(String name,List<Person> friends) {
    Person newPerson=new Person();
    newPerson.setName(name);
    newPerson.setFullName(new FullName("zjj_first", "zjj_middle", "zjj_last"));
    newPerson.setAge(24);
    List<String> hobbies=new ArrayList<String>();
    hobbies.add("篮球");
    hobbies.add("游泳");
    hobbies.add("coding");
    newPerson.setHobbies(hobbies);
    Map<String,String> clothes=new HashMap<String, String>();
    clothes.put("coat", "Nike");
    clothes.put("trousers", "adidas");
    clothes.put("shoes", "安踏");
    newPerson.setClothes(clothes);
    newPerson.setFriends(friends);
    return newPerson;
  }
}

说明一下,上面的代码中

ResultExporter.exportResult("JSON序列化性能", results, "count", "秒");

这个是我自己编写的将性能测试报告数据填充至Echarts图,然后导出png图片的方法,具体代码我就不贴了,参考我的github源码。

执行后的结果图:

 

从上面的测试结果可以看出,序列化次数比较小的时候,Gson性能最好,当不断增加的时候到了100000,Gson明细弱于Jackson和FastJson, 这时候FastJson性能是真的牛,另外还可以看到不管数量少还是多,Jackson一直表现优异。

JSON反序列化性能基准测试

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JsonDeserializeBenchmark {
  /**
   * 反序列化次数参数
   */
  @Param({"1000", "10000", "100000"})
  private int count;

  private String jsonStr;

  public static void main(String[] args) throws Exception {
    Options opt = new OptionsBuilder()
        .include(JsonDeserializeBenchmark.class.getSimpleName())
        .forks(1)
        .warmupIterations(0)
        .build();
    Collection<RunResult> results = new Runner(opt).run();
    ResultExporter.exportResult("JSON反序列化性能", results, "count", "秒");
  }

  @Benchmark
  public void JsonLib() {
    for (int i = 0; i < count; i++) {
      JsonLibUtil.json2Bean(jsonStr, Person.class);
    }
  }

  @Benchmark
  public void Gson() {
    for (int i = 0; i < count; i++) {
      GsonUtil.json2Bean(jsonStr, Person.class);
    }
  }

  @Benchmark
  public void FastJson() {
    for (int i = 0; i < count; i++) {
      FastJsonUtil.json2Bean(jsonStr, Person.class);
    }
  }

  @Benchmark
  public void Jackson() {
    for (int i = 0; i < count; i++) {
      JacksonUtil.json2Bean(jsonStr, Person.class);
    }
  }

  @Setup
  public void prepare() {
    jsonStr="{\"name\":\"邵同学\",\"fullName\":{\"firstName\":\"zjj_first\",\"middleName\":\"zjj_middle\",\"lastName\":\"zjj_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":[{\"name\":\"小明\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null},{\"name\":\"Tony\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null},{\"name\":\"陈小二\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null}]}";
  }

  @TearDown
  public void shutdown() {
  }
}

执行后的结果图:

 

从上面的测试结果可以看出,反序列化的时候,Gson、Jackson和FastJson区别不大

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 点我我会动 设计师:白松林 返回首页