[开发者心得] JsonEx库&String压缩库

[复制链接]
755 |0
逝水无痕 发表于 2023-2-22 11:01:33 | 显示全部楼层 |阅读模式
1.前言

这两个库是为了降低字符串的长度。

2.数据对比
Json 压缩

源Json大小(kb)
parse 耗时 (ms)
stringify 耗时 (ms)
序列化后json大小(kb)
源数据长度
压缩率 (%)
Json
33.67
<1
<1
33.67
34488
100%
JsonEx
33.67
2
3
13.19
34488
60.82%
JsonEx
159.41
18
7
71.62
163244
55.07%

String 压缩

源数据长度
压缩后长度
源大小(kb)
压缩后大小(kb)
压缩耗时(ms)
压缩率 (%)
compress
13511
8958
13.19
8.74
630
33.73%
compress
34488
18411
33.67
17.97
3,303
46.62%
compress
73341
51930
71.62
50.71
20,723
29.19%

deCompress 耗时

源数据长度
源数据大小(kb)
耗时(ms)
deCompress
51930
50.71
16

3.结论
JsonEx:对比Json,提供50%+的文本压缩率。耗时是划算的。
string压缩:这个只能在研发或预配置阶段使用,Runtime环境中不建议使用该工具,压缩太耗时了,Runtime阶段只能使用配置好的string数据,压缩后的,直接解压。

4.Code
JsonEx:
    Tips:Json序列化对象中不可以使用匿名类型,不可以使用Map
           需要将所有Map都换为List,否则无法合并结构(会降低压缩率或压缩失败)。

/*
* @Description: JsonEx 带压缩的Json库
*/
export namespace JsonEx {

    const key_parseObjKey = "key_parseObjKey";
    const key_parseObjVal = "key_parseObjVal";

    /**
     * 获取对象json字符串
     * @param obj 序列化对象
     * @returns
     */
    export function stringify(obj: any): string {

        let any = {};
        any[key_parseObjKey] = {};
        any[key_parseObjVal] = [];
        compressObj(obj, any, any[key_parseObjKey], any[key_parseObjVal]);

        let str = "";
        str = JSON.stringify(any);

        return str;

    }

    /**
     * 压缩对象
     * @param targetObj
     * @param outObj
     * @param keysList
     * @param valsList
     */
    function compressObj(targetObj: any, outObj: any, keysList: any, valsList: any): any {

        Object.keys(targetObj).forEach((k, i, arr) => {

            let curObj = targetObj[k];

            let useKey = k;

            if (Array.isArray(targetObj) && i > 0) {
                useKey = "0";
            }

            if (Array.isArray(curObj)) {
                keysList[useKey] = [];
                let vals = [];
                valsList.push(vals);
                compressObj(curObj, keysList[useKey], keysList[useKey], vals)
            } else if (curObj instanceof Object) {
                keysList[useKey] = {};
                let vals = [];
                valsList.push(vals);
                compressObj(curObj, keysList[useKey], keysList[useKey], vals)
            } else {

                let typeStr = typeof curObj;

                if (typeStr === "string") {
                    keysList[k] = "";
                } else if (typeStr === "number") {
                    keysList[k] = 0;
                } else if (typeStr === "boolean") {
                    keysList[k] = false;
                } else {
                    keysList[k] = null;
                }

                valsList.push(curObj);

            }

        });

    }

    /**
     * 解压缩对象
     * @param objClass
     * @param objdata
     * @param outObj
     */
    function decompressObj(objClass, objdata, outObj) {

        // 获取keys
        let classkeys = Object.keys(objClass);
        let dataKeys = Object.keys(objdata);
        
        let objdataType = typeof objdata;

        // 遍历 objClass 的结构
        for (let i = 0; i < classkeys.length; i++) {

            // 获取class key
            let key = classkeys;
            // 获取 field
            let field = objClass[key];

            // 获取 data
            let data = objdata[dataKeys];

            // 如果 objdata 是基础类型,则直接 赋值 否则 则赋值结构数据
            if (objdataType == "string" || objdataType == "number" || objdataType == "boolean") {
                data = objdata;
            }

            // 返回实例是否有当前字段
            let has = true;

            // 获取结构字段 类型
            let typeClazzStr = typeof field;

            // 返回实例 是否有字段
            if (!outObj[key]) {
                has = false;
            }

            // 当前字段是否为数组
            if (Array.isArray(field)) {

                // 返回实例没有当前字段,则初始为数组
                if (!has) {
                    outObj[key] = [];
                }

                // 遍历数据
                data.forEach((e, i2, arrs) => {

                    // 设置返回实例的结构
                    outObj[key].push({});

                    // 递归解析字段对象
                    decompressObj(field[0], data[i2], outObj[key][i2]);

                })

                continue

            } else if (typeClazzStr === "string") {
                outObj[key] = data;
                continue;
            } else if (typeClazzStr === "number") {
                outObj[key] = data;
                continue;
            } else if (typeClazzStr === "boolean") {
                outObj[key] = data;
                continue;
            }

            // 当前 field 是 object 类型

            outObj[key] = {};

            // 当前数据是数组
            if (Array.isArray(data)) {
                // 递归解析当前字段
                decompressObj(field, data, outObj[key]);
                continue;
            }

            // 递归解析
            decompressObj(field, data, outObj[key]);

        }

    }

    /**
     * 解析对象
     * @param jsonString JSON字符串
     * @param outInstance 输出对象实例
     * @returns 解析后的对象
     */
    export function parse<T>(jsonString: string, outInstance: T): T {

        // 解析JSON字符串
        let obj = JSON.parse(jsonString);

        // 检查obj中是否存在key_parseObjVal和key_parseObjKey属性
        if (obj[key_parseObjVal] && obj[key_parseObjKey]) {

            // 如果存在,调用decompressObj函数
            decompressObj(obj[key_parseObjKey], obj[key_parseObjVal], outInstance);

        }

        // 返回解析后的对象
        return outInstance;
    }

}


StringsUtil:
    Tips:字符串不能带以下字符:    `  ~  ! @  #  $  %  ^  &  <  >

/*
* @Description: 字符串压缩库
*/
export namespace stringUtls {

    /**
     * 计算函数运行时间
     * @param fun
     */
    export function logicTime(fun: () => void) {

        let startTime = Date.now();
        fun();
        let endTime = Date.now();

    }

    /**
     * 替换所有字符串
     * @param strContent 源内容
     * @param oldContent 旧的内容
     * @param newContent 新的内容
     * @returns 新内容
     */
    function replaceAll(strContent, oldContent, newContent): string {

        let back = strContent + "";
        let str = strContent + "";
        let out = "";

        let isHas = newContent.indexOf(oldContent) != -1;

        while (true) {
            out = "";
            let modify = false;
            while (true) {
                let index = str.indexOf(oldContent);
                if (index == -1) {
                    out += str;
                    break;
                }
                modify = true;
                out += str.slice(0, index);
                out += newContent;
                str = str.slice(index + oldContent.length, str.length);
            }
            if (out == "") out = str;

            if (!modify || isHas) break;
            str = out;

        }

        return out;

    }

    /**
     * 获取匹配字符串数量
     * @param strContent 源内容
     * @param findStr 需要查找的内容
     * @returns 数量
     */
    function count(strContent, findStr): number {

        let oldContent = findStr + "";
        let count = 0;

        let back = strContent + "";
        let str = strContent + "";
        let out = "";

        let isHas = false;

        while (true) {
            out = "";
            let modify = false;
            while (true) {
                let index = str.indexOf(oldContent);
                if (index == -1) {
                    out += str;
                    break;
                }
                modify = true;
                count++;
                out += str.slice(0, index);
                str = str.slice(index + oldContent.length, str.length);
            }
            if (out == "") out = str;

            if (!modify || isHas) break;
            str = out;

        }

        return count;
    }

    /**
     * 检测是否有字符
     * @param str 源内容
     * @returns 是否有
     */
    function checkHolder(str: string): boolean {

        const holder = ['`', '~', '!', '@', '#', '$', '%', '^', '&', '<', '>'];

        for (let i = 0; i < holder.length; i++) {
            if (str.indexOf(holder) != -1) return true;
        }

        return false;
    }

    /**
     * 获取占位符
     * @param num 当前占位的num值
     * @returns 占位符
     */
    function getPlaceHolder(num: number): string {

        let def = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm']
        let defLength = def.length;

        if (num >= 0 && num < defLength) {
            return def[num] + "`";
        } else if (num >= def.length && num < defLength * 2) {
            return def[(num % defLength)] + `~`;
        } else if (num >= def.length * 2 && num < defLength * 3) {
            return def[(num % defLength)] + `!`;
        } else if (num >= def.length * 3 && num < defLength * 4) {
            return def[(num % defLength)] + `@`;
        } else if (num >= def.length * 4 && num < defLength * 5) {
            return def[(num % defLength)] + `#`;
        } else if (num >= def.length * 5 && num < defLength * 6) {
            return def[(num % defLength)] + `$`;
        } else if (num >= def.length * 6 && num < defLength * 7) {
            return def[(num % defLength)] + `%`;
        } else if (num >= def.length * 7 && num < defLength * 8) {
            return def[(num % defLength)] + `^`;
        } else if (num >= def.length * 8 && num < defLength * 9) {
            return def[(num % defLength)] + `&`;
        } else if (num >= def.length * 9 && num < defLength * 10) {
            return def[(num % defLength)] + `<`;
        }

        return num.toString(16) + ">";

    }

    /**
     * 获得重复列表(降序)
     * @param content 原内容
     * @param minFindConLength 重复内容最小长度
     * @param maxFindConLength 重复内容最大长度
     * @returns 重复列表(降序)
     */
    function logicRepeat(content: string, minFindConLength: number = 8, maxFindConLength: number = 50): any[] {

        let res = [];

        let logicIndex = 0;

        let curLogicIndexLength = Math.max(minFindConLength, (getPlaceHolder(logicIndex)).length + 1);

        let temp = content + "";

        let tempCurFindCount = 0;

        for (let i = 0; i < temp.length; i++) {

            if (i + curLogicIndexLength > temp.length) break;

            let find = temp.slice(i, i + curLogicIndexLength) + "";
            if (checkHolder(find)) continue;
            tempCurFindCount = count(temp, find)

            if (tempCurFindCount > 1) {

                for (let j = 1; j < maxFindConLength; j++) {

                    if (curLogicIndexLength + j + i >= temp.length) break;

                    let find2 = temp.slice(i, i + curLogicIndexLength + j)

                    if (checkHolder(find2)) break;

                    let find2Count = count(temp, find2)
                    if (find2Count > tempCurFindCount) {
                        find = find2 + "";
                        tempCurFindCount = find2Count;
                        continue
                    }
                    break;

                }

                // 过滤掉了重复次数少于10次的文本
                if (tempCurFindCount < 10) continue;

                if (res.findIndex(e => { return e[0] === (find) }) != -1) continue;

                res.push([find, getPlaceHolder(logicIndex)]);
                logicIndex++;
                curLogicIndexLength = Math.max(minFindConLength, (getPlaceHolder(logicIndex)).length + 1);
                continue;
            }

        }

        res = res.sort((a, b) => {
            return b[2] - a[2];
        })
        res.forEach((e, i, arrs) => {
            e[1] = getPlaceHolder(i);
        })

        return res;

    }

    /**
     * 压缩字符串
     * @param content 源内容
     * @returns 压缩后的内容
     */
    export function compress(content: string): string {
        let temp = content + "";
        let res2 = {};

        let res = logicRepeat(content);
        let k1 = "";
        res.forEach((e, i, arrs) => {
            temp = replaceAll(temp, e[0], e[1]);
            k1 += e[0];
            if (i < res.length - 1) {
                k1 += "|";
            }
        })
        res2["k"] = k1;
        res2["v"] = temp;

        console.error(res2);

        return JSON.stringify(res2);
    }

    /**
     * 解压缩字符串
     * @param content 源内容
     * @returns 解压缩后的内容
     */
    export function deCompress(content: string): string {

        let obj = JSON.parse(content);
        if (!obj["k"] || !obj["v"]) return "";
        let res = obj["v"] + "";

        let key1 = obj["k"] + "";
        let key1List = key1.split('|');

        for (let i = key1List.length - 1; i--; i >= 0) {

            res = replaceAll(res, getPlaceHolder(i), key1List);

        }

        return res;
    }

}
回复

使用道具 举报

热门版块
快速回复 返回顶部 返回列表