【我的Android进阶之旅】如何压缩Json格式数据,减少Json数据的体积?

一、背景

最近刚刚做完一个中文汉字笔画排序的功能,链接如下:

其中优化之后,将数据库的内容,序列化成为了json数据,然后通过解析json数据,拿到汉字笔画的相关信息。但是未处理前的json文件,体积较大,有2.13Mb,因此需要压缩才行。

部分数据如下所示:

在这里插入图片描述
{
      "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"
      }
}

二、常规压缩json

2.1 未处理前的json文件

未处理前的json文件,格式好看但是体积较大。

在这里插入图片描述
在这里插入图片描述

未处理前的json文件,一共占用125414行


在这里插入图片描述

未处理的原始json文件大小为2.13Mb


在这里插入图片描述

2.2 将JSON压缩成一行,去掉换行和空格字符

在这里插入图片描述

在Android Studio中打开,如下所示:

在这里插入图片描述

将JSON压缩成一行,去掉换行和空格字符后的json文件大小为:1.39Mb,只之前的2.13Mb小了整整0.74Mb,这个在移动端是很可观的优化!

在这里插入图片描述

2.3 将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里面的属性,这样解析的时候就不会出错了。


在这里插入图片描述

2.4 常规总结

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

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

已经算很给力了,但是这个json文件还是有1.04Mb啊,是否还可以进行压缩呢?答案是肯定的,我们下面介绍下使用算法对该json文件进行压缩。

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

3.1 使用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();
    }
}

3.1.1 压缩原始的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文件,如下所示:

在这里插入图片描述

3.1.2 还原成原始的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);

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

3.1.3 Deflater压缩总结

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

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

优化步骤 体积
1.未处理的原始json 2.13MB
2.将JSON压缩成一行,去掉换行和空格字符 1.39MB
3.将JSON的key进行缩短 1.04MB
4.使用Deflater压缩json,Base64编码 0.38MB
在这里插入图片描述
在这里插入图片描述

3.2 使用Gzip压缩解压json

在我封装的http库里面,有对请求json数据进行Gzip压缩,对服务器返回的json数据进行Gzip解压。这里也来试一下Gzip压缩json。

编写一个 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));
    }

}

3.2.1 压缩原始的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优秀!

在这里插入图片描述

3.2.2 还原成原始的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解析一切正常!

3.2.3 Gzip压缩总结

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

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

四、 其他压缩算法

除了上面的算法之外,我们还可以使用很多其他的压缩算法,进一步压缩json的体积。我们的原始json中还是有很多重复的key值可以进行优化的,下面的算法中有部分可以进行key优化!

常见的json压缩算法有CJSON与HPack,其原理都是将key和value进行抽离,节省掉部分的重复的key值造成的空间消耗。

4.1 CJSON

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

原始JSON:

[{
        "x": 100,
        "y": 100
    },
    {
        "x": 100,
        "y": 100,
        "width": 200,
        "height": 150
    },
    {}
]
在这里插入图片描述

CJSON压缩后:

{
    "templates": [
        [0, "x", "y"],
        [1, "width", "height"]
    ],
    "values": [{
            "values": [1, 100, 100]
        },
        {
            "values": [2, 100, 100, 200, 150]
        },
        {}
    ]
}
在这里插入图片描述

4.2 HPack

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

[
    {
         "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的资料可能反而会比较多。

压缩效果

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

在这里插入图片描述

五、参考资料

在这里插入图片描述

image

作者:欧阳鹏 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:https://blog.csdn.net/qq446282412/article/details/83856439
☞ 本人QQ: 3024665621
☞ QQ交流群: 123133153
☞ github.com/ouyangpeng
oypcz@foxmail.com
如果本文对您有所帮助,欢迎您扫码下图所示的支付宝和微信支付二维码对本文进行打赏。

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容