私には、宛先として UTF-8 を使用して文字列を圧縮するのは合理的ではないように思えます... トラブルを探しているだけのようです。伝送サイズが重要な場合は、圧縮をいくらか落として、プレーンな 7 ビット ASCII を宛先として使用する方がよいと思います。
ストレージ制限が UTF-16 文字に基づいている場合、エスケープまたは UTF-16 準拠を気にする場合は、大きな安全なサブセットを探すことができます。または、他のすべてが関係する場合は、各文字を 0..65535 として使用することもできます (例:データベース) には問題はありません。ほとんどのソフトウェア層はその (ab) 使用に問題はないはずですが、UTF-16 の範囲では 0xD800-0xDFFF が特別な用途 (サロゲート ペア) のために予約されているため、一部の組み合わせは正式には「エンコード エラー」であり、理論的には停止または停止できることに注意してください。歪。
私が面白半分に書いた4 KB の JavaScript デモでは、4 バイナリ バイトを、JavaScript 文字列 (85^5は 8^4 よりわずかに大きいですが、JavaScript 整数の精度に収まります)。これにより、エスケープする必要なく、たとえばJSONの圧縮データが安全になります。
次のコードでは、85 個の「安全な」文字のリストを作成します。
let cset = "";
for (let i=35; i<35+85+1; i++) {
if (i !== 92) cset += String.fromCharCode(i);
}
次に、4 バイト ( b0
、b1
、b2
およびb3
それぞれ 0...255 から) を 5 文字にエンコードするコードは次のとおりです。
// First convert to 0...4294967295
let x = ((b0*256 + b1)*256 + b2)*256 + b3;
// Then convert to base 85
let result = "";
for (let i=0; i<5; i++) {
let x2 = Math.floor(x / 85);
result += cset[x - x2*85];
x = x2;
}
デコードするには、逆の手順を実行します。つまり、base-85 の数値から x を計算し、base-256 の 4 桁 (つまり、バイト) を抽出します。
注: トーラス コードでは、 92 をスキップする代わりに、わずかに異なる文字セットを使用し、\
126 に置き換えました~
。興味のある方は、完全な解凍コードをご覧ください。
// There are two Huffman-encoded code streams
// T - single chars (0..127) and sequence lengths (128...255)
// A - high bits of relative addresses of sequence (0..255)
//
// Expansion algorithm is:
// 1) Read a code X from T
// 2) If it's a char (X < 128) then add to output
// 3) otherwise (X>=128) read sequence address ADDR from stream A (high bits)
// and from input (low bits) and copy X-128 bytes from ADDR bytes "ago"
//
let Z = 5831; // expanded size
let i = 0, // source ptr
a = 0, // current bits accumulator
n = 0; // number of available bits in a
// Read a single bit
let b = function(){
if (!n) {
// There are no more bits available in the accumulator, read a new chunk:
// 5 ASCII escape-safe chars will be transformed in 4 8-bit binary bytes
// (like BASE64, just a bit more dense)
a = 0;
let w = 5;
while (w--) {
let y = s.charCodeAt(i+w); // get next char
a = a*85 + (y > 125 ? 92 : y) - 35; // extract base-85 "digit" (note, uses ~ instead of \ that requires quoting)
}
n = 32; // we got 32 bits in a
i += 5; // we consumed 5 characters from source
}
return (a >> --n) & 1; // extract a single bit
};
// Read a code of z bits by concatenating bits coming from b()
let v = function(z){
return (--z ? v(z) : 0)*2+b();
};
// Read an Huffman (sub-)tree: a bit will tell if we need to
// read a two sub-trees or a leaf
let h = function(){
return b() ? [h(), h()] : v(8);
};
// Read A and T Huffman trees
let A = h(), T = h();
// Extract a code given a node:
// if the node is an array (intermediate node) then we need to read a bit
// from the input binary stream to decide which way to go down the tree,
// if it's a number then we just return the value.
// `n.map` is truthy for arrays and falsy for numbers.
let d = function(n){
return n.map ? d(n[b()]) : n;
};
let S=""; // Output
// While we're not done
while(S.length<Z){
// Extract a code from T
x = d(T);
if (x < 128) {
// This is a single character, copy to output
S += String.fromCharCode(x);
} else {
// This is a sequence of x-128 bytes, get address and copy it
// Note: high 8 bits are from the Huffman tree A and 8 low bits
// are instead directly form the bit stream as they're basically
// noise and there's nothing to gain by trying to compress them.
S += S.substr(S.length-(d(A)<<8)-v(8), x-128)
};
}
(この再フォーマット/コメント付きバージョンはテストしていないことに注意してください。タイプミスがある可能性があります)