自社開発の文字列圧縮アルゴリズム

概要

開発では、オンラインスタックがオンラインの問題を分析して処理することが報告されるシナリオがよくあります。したがって、スタックの圧縮と暗号化も不可欠です。暗号化:AES対称暗号化アルゴリズムを使用できます。圧縮:文字列は、アップロード時にprotobufのネイティブ圧縮率を使用して圧縮できます。

ただし、トラフィックを節約し、伝送効率を向上させるために、スタックをアップロードする前にデータを1回圧縮することで保証できます。以下では、作者が文字列を圧縮するために自分で調べた、独自の暗号化効果を持つアルゴリズムを紹介します。

アルゴリズムの紹介

このアルゴリズムはシナリオを使用します:制限された文字セットでの文字列圧縮

たとえば、Javaメソッドの完全修飾名の圧縮、完全修飾メソッドの場合、コンポーネント:大文字と小文字の英字、数字、特殊文字。開発プロセスでは、標準の修飾されたクラス名とメソッド名がその名前に精通している必要があります。効果的な統計によると、99%以上のメソッドが大文字と小文字の英字で完全に修飾されています。

アルゴリズムの実装

圧縮原理の簡単な紹介

char文字の空きビットを使用して、有効なデータを格納します。たとえば、a〜zを1から26までの数値にマッピングし、Charタイプを高、中、低の3つのグループに分割し、グループとして5ビットを使用すると、数値が個別に格納されます(この数値は文字を表します)。

文字列ヘッダー構造を作成します:Head

Javaコードを記述する過程で、完全修飾文字列の大文字の割合は比較的小さいため、完全修飾文字列の大文字は、先行する補助文字を使用して記録されます。文字列が有限で不変の場合、それらを構成する文字の相対位置が決定されます。実装アルゴリズムは次のとおりです。

public char[] build(String s) {
            ...
    for (int i = 0; i < len; i++) {
        c = s.charAt(i);
        b = Character.isUpperCase(c);
        if (b || c == FILL) {
            if (i - lastIndex >= maxDistance) {
                maxDistance = i - lastIndex;
            }
            upCharIndex.add(i - lastIndex);
            lastIndex = i;
       }
    if (b) upCharCount++;
    }
    ...
    return handleHead(type);
}

复制代码

圧縮前の最初のステップ:文字列の先頭で、大文字の位置と各大文字間の距離を保存して記録します。(小数点は大文字と見なされます)。


private char[] handleHead(int type) {
        ...
    int k, j;
    //记录大写字母位置与char中
    for (int i = 0; i < chars.length; i++) {
        if (i == 0) {
            for (j = 0, k = 1; j < ch1; j++, k++) {
                ch = bitToLeft(ch, upCharIndex.get(j), 12 - (k * stepDistance));
            }
            chars[i] = ch;
        } else {
            char emptyCh = FILL;
            emptyCh &= 0;
            int start = (i - 1) * sizeOfChar + ch1;
            for (j = start, k = 1; j < start + sizeOfChar; j++, k++) {
                if (j == upCharIndex.size())
                    break;
                emptyCh = bitToLeft(emptyCh, upCharIndex.get(j), 16 - (k * stepDistance));
            }
            chars[i] = emptyCh;
        }
    }
    return chars;
}

复制代码

ヘッドの最小長は1 Char、つまり16ビットです。ステップサイズは16ビットの上位2ビットに格納されます。次の2ビットは、実際のヘッド長を記録します。

head长度:Head最小的长度是1个Char,其中记录步长和Head长度的信息。目前,填充长度最长为 3+1,可通过步长算法完成Head长度的扩展。扩展方法:getTypeBySize、getSizeByType

  • 存储大写字母的位置时,按照步长来填充。例如:步长为3,那么就意味着每3个bit存储一个大写字母位置。
  • Head的长度取决于填充了多少个步长。例如:填充10个步长为3的位置,需要16%3等于5,那么就需要两个Char.

步长: 步长是一个可变的量,在算法设计中,提供如下几种步长类型:(据统计最长英文单词:45个字符)

  • STEP_0:表示没有大写字母
  • STEP_3:表示大写字母距离(0,8),步长为3
  • STEP_15:表示大写字母间距离[8,16),步长为4
  • STEP_OVER_15:表示大写字母间距离[16,63),步长为6

建立压缩字符串内容:Content

Content压缩是按照1个Char的高、中、低三位中分别存储一个字符的算法完成的。具体的实现FormatUtil.ContentBuilder

填充: 由于字符串并不都是3的倍数。为了保证原字符串的完整性,在分割字符串之前先给原来字符串填充一定数量的字符,保证其在分割的时候可以每3个字符为一组。


public String handleString(String s) {
    int f;
    if ((f = s.length() % 3) != 0) {
        StringBuilder sBuilder = new StringBuilder(s);
        for (f = 3 - f; f > 0; f--)
            sBuilder.append(FILL);
        s = sBuilder.toString();
    }
    return s.toLowerCase();
}

复制代码

分割替换: 在完成填充以后,将原来的字符串以三个为一组分割成多个组。对于字符串中的数字或者特殊字符,我们在mapping文件中并没有形成映射,因此,一旦出现,那么就通过“MASK”去代替。

public short buildShort(char high, char mid, char low) {
    short b = 0;

    b |= getShortFromMapping(high) << 10;
    b |= getShortFromMapping(mid) << 5;
    b |= getShortFromMapping(low);
    return b;
}

public short getShortFromMapping(char ch) {
    if (mapping.containsKey(ch))
        return mapping.get(ch);
    return mapping.get(MASK);
}
复制代码

建立完成压缩后字符串

Head + content = 压缩完成后的字符串。

总结

在算法构思前期,理论压缩效率可达66%:将三个Char存储在一个Char中,不过从最后包大小的总压缩率来计算,压缩率应该只有50%左右。出现这种的情况的原因如下:

  • 字符串长度不都是3的整数倍,有多余的字符填充
  • 压缩完以后的字符并不是一个正确的ASCII码,在Java底层对字符集的编解码过程中,将其认为是汉字,一次一个字符会被解码成两个字符大小。

完整代码 欢迎大家评论留言,指导学习!

おすすめ

転載: juejin.im/post/7079610382548992013