Varios métodos de compresión comunes para JSON

No importa qué lenguaje de programación se utilice, los datos en formato json se han utilizado ampliamente, ya sea transmisión o almacenamiento de datos. En muchos escenarios de aplicaciones, es posible que desee comprimir aún más la longitud de la cadena JSON para mejorar la eficiencia de la transmisión. Es una base de datos nosql. Es posible que desee comprimir aún más la longitud de la cadena json para ahorrar espacio de almacenamiento. A continuación, presentaré la implementación de la tecnología de compresión de datos json más utilizada (CJSON y HPack).

1. CJSON

El algoritmo de compresión de CJSON separa principalmente los datos en Plantilla y Valor para guardar "valores clave" duplicados.

datos sin procesar:

[
  {     "x": 100,
    "y": 100
  }, {   "x": 100,
    "y": 100,
    "width": 200,
    "height": 150
  },
  {},
]

Después de la compresión:

{
  "templates": [
    [0, "x", "y"], [1,"width", "height"] 
  ],
  "values": [
    { "values": [ 1,  100, 100 ] },
    { "values": [2, 100, 100, 200,150 ] },
    {}
  ] 
}

       2. HPACK

El algoritmo de compresión de HPack también separa clave y valor: el primer valor de la matriz es la plantilla de HPack y los siguientes valores son valor.

hpack es un programa de compresión de conjuntos de datos sin pérdidas, en varios idiomas y centrado en el rendimiento. Puede reducir la cantidad de caracteres utilizados para representar colecciones isomórficas genéricas en un 70%.
Este algoritmo proporciona múltiples niveles de compresión (de 0 a 4).
La compresión de nivel 0 realiza la compresión más básica eliminando claves (nombres de propiedad) de una estructura que crea un encabezado con cada nombre de propiedad en el índice 0. El siguiente nivel permite una mayor reducción del tamaño de JSON al asumir la presencia de entradas duplicadas.
datos sin procesar:

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

Después de la compresión:

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

Conclusión
Ambos métodos se centran principalmente en extraer claves JSON para construir un índice de manera unificada, pero el formato final es diferente. El formato simplificado de HPack tiene muchos menos caracteres que CJSON, por lo que la eficiencia de compresión de HPack es relativamente alta. Si el contenido JSON es muy pequeño , CJSON Puede haber más información.

3. Al estudiar el código fuente de la herramienta de análisis de rendimiento de código abierto PINPOINT, descubrí que utiliza un método con una relación de compresión más alta.

Por ejemplo:

datos sin procesar:

{
  name : "Andrea",
  age : 31,
  gender : "Male",
  skilled : true
}

El diagrama comprimido es el siguiente:

Los datos comprimidos se convierten en una cadena de datos binarios. El nombre y el género son tipos de cadenas con longitudes variables, por lo que sus primeros cuatro dígitos se utilizan para representar la longitud binaria del valor "Andrea" correspondiente al nombre. Otros tipos El valor de los datos es como se muestra en la API a continuación:

        Este enfoque puede considerarse un tipo de compresión de cifrado: si el receptor de datos no conoce la estructura de los datos, no puede analizar directamente el valor objetivo. El remitente y el receptor de datos deben ponerse de acuerdo sobre la estructura de los campos.

       De los ejemplos anteriores, encontramos que tanto CJSO como HPack solo guardan el tamaño de la clave de datos json, pero los corchetes y las comillas dentro son inútiles y redundantes. El método de compresión que presenté anteriormente puede ser muy complejo de usar. , pero la relación de compresión puede ser mejor que las dos anteriores, ya sea que se use para almacenamiento o transmisión de datos, puede ahorrar muchos recursos.

4. Utilice GZIP para comprimir y descomprimir 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();
        }
    }
}

Utilice GZIP para comprimir y descomprimir Json, principalmente utilizando java.util.zip.GZIPInputStream y java.util.zip.GZIPOutputStream.

Método de compresión:

    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");
    }

Método de descompresión:

    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”);
    }

Pruebe usando una cadena Json de 31,8k:

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

Los resultados de la prueba son:

Una clase de herramienta para compresión, descompresión y codificación Gzip usando 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));
    }
 
}

Comprimir datos STROKE.JSON sin procesar

  //原始文件   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");

Después de ejecutarlo, exporte gzipStrokeJson.json en la tarjeta SD y colóquelo en el directorio de activos para su posterior análisis.

El archivo gzipStrokeJson.json exportado tiene un tamaño de 405 kb, que no es mejor que el tamaño de 387 KB después de usar Deflater para comprimir json hace un momento.

Restaurar a los datos STROKE.JSON originales

Desactivar la compresión no es suficiente. Tenemos que usar datos de archivos json comprimidos, por lo que también necesitamos descomprimir los datos json comprimidos. La operación es la siguiente:

//使用 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);

¡Después de la descompresión, el análisis json es normal!

Resumen de compresión GZIP

Después de las operaciones de rutina anteriores,
el tamaño de nuestro archivo json se ha reducido a 405 KB .
Aunque no es tan bueno como la compresión de Deflater en este momento: 387 KB , es 662 KB más pequeño que
los datos originales sin el algoritmo de compresión de 1067 KB .

La tasa de compresión es del 62,04% y el volumen comprimido es del 37,95% del original , ¡lo cual también es bueno!

5. Utilice un algoritmo de compresión para comprimir.

 Utilice DEFLATER para comprimir JSON e INFLATER para descomprimir JSON.

Deflater es un algoritmo de compresión de datos sin pérdidas que utiliza tanto el algoritmo LZ77 como la codificación Huffman .

Puede utilizar las clases Deflater e Inflador proporcionadas por Java para comprimir y descomprimir JSON, la siguiente es la clase de herramienta

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

 Comprimir datos STROKE.JSON sin procesar

Luego, primero comprimimos los datos originales de Stroke.json en 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");

El método writeFile es el método para escribir en la tarjeta SD.

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));
                }
            }
        }
    }

Después de ejecutarlo, exporte deFlaterStrokeJson.json en la tarjeta SD y colóquelo en el directorio de activos para su posterior análisis.

Insertar descripción de la imagen aquí

Utilice Deflater para comprimir json, el tamaño comprimido es 387 KB, que es mucho más pequeño que la última vez que fue 1067 KB.

El archivo deFlaterStrokeJson.json después de la compresión Deflater y la codificación Base64 es el siguiente:

Insertar descripción de la imagen aquí

Restaurar a los datos STROKE.JSON originales

Desactivar la compresión no es suficiente. Tenemos que usar datos de archivos json comprimidos, por lo que también necesitamos descomprimir los datos json comprimidos. La operación es la siguiente:

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

Después de la descompresión, ¡todo funciona normalmente! ¡Perfecto!

Resumen de compresión DEFLATER

Después de las operaciones de rutina anteriores,
el tamaño de nuestro archivo json se ha reducido a 387 KB , que es 680 KB más pequeño que los datos originales de 1067 KB
sin utilizar el algoritmo de compresión .

La tasa de compresión es del 63,73% y el volumen después de la compresión es del 36,27% del volumen original.

Pasos de optimización volumen
1. Json sin procesar sin procesar 2,13MB
2. Comprima JSON en una línea, eliminando nuevas líneas y espacios. 1,39MB
3. Acorte la clave JSON 1.04MB
4. Utilice Deflater para comprimir la codificación json y Base64 0,38MB

6. Acorte la CLAVE JSON

JSON es una estructura clave-valor, si las especificaciones están bien definidas, la clave se puede acortar tanto como sea posible, incluso a letras sin sentido, pero la premisa es que el documento debe estar escrito con claridad para evitar problemas innecesarios.

Por ejemplo, la estructura clave-valor anterior es la siguiente:

{
      "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"
      }
}

Ahora optimizaremos la clave y usaremos

c reemplaza el código
n reemplaza el nombre
o reemplaza el orden
s reemplaza la suma de trazos

El tamaño del archivo JSON después de acortar y optimizar la clave JSON es: 1,77 Mb, que es solo 0,36 Mb más pequeño que los 2,13 Mb anteriores. ¡Esta es una optimización considerable en el terminal móvil!

Luego, repita la operación de [2.2 Comprimir JSON en una línea, eliminar líneas nuevas y espacios] para el archivo después de acortar la clave.

Eche otro vistazo al tamaño del archivo, que es 1,04 Mb, que es 1,09 Mb más pequeño que los datos originales de 2,13 Mb. ¡Esta es una optimización considerable en el lado móvil!

Por supuesto, si el nombre de la clave cambia, también se debe modificar el bean de entidad de Java correspondiente al análisis de Json.

Debido a que uso jackson para analizar json, uso la anotación @JsonProperty para indicar que el archivo json modificado corresponde a las propiedades del bean Java original, de modo que no habrá errores durante el análisis.

Resumir

Después de las operaciones de rutina anteriores,
el tamaño de nuestro archivo json se ha reducido a 1,04 Mb , que es 1,09 Mb más pequeño
que los datos originales de 2,13 Mb .

La tasa de compresión es del 51,174% y el volumen después de la compresión es del 48,826% del original.

 Fuente de referencia: [Mi viaje avanzado de Android] ¿Cómo comprimir datos en formato Json y reducir el tamaño de los datos Json? -Jianshu

Referencias

Varios métodos de compresión JSON comunes: Gray Letter Network (agregación de blogs de desarrollo de software)

 

Supongo que te gusta

Origin blog.csdn.net/yyongsheng/article/details/132080304
Recomendado
Clasificación