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