7bits数値配列をutf8形式で効率的に出力する方法を紹介します。
ES6のtemplate literalのおかげで、通常のliteralでは無効な多くの制御文字を置くことができますが、それでも\r、$、\、`の4種の文字はescapeする必要があります。そうなると、文字列を記述する際に元より大幅に文字数が増える可能性があります。
そこで、base122を参考に膨張率をほぼ0に抑える工夫を考えます。2bytesのUTF8符号には約11bitsの余裕があるため、無効なbyteが現れた場合、それを次のbyteに併合して、escape無しで1文字として保存できます。これは2 + 7 = 9bitsしか要しません。
長所
- 高速高効率
- 復号処理の記述がごく少量で済む
短所
- 復号結果に0が1個余計に溢れる事がある(元の大きさが不明な場合)
- 使い所さんが限定的
-
\0は無変換なので転写不能. fileに書き込んで利用するという面倒仕様
実装編
/*
b7utf8.encode(In,toB)
@In: 要素が0~127の数値配列
@toB: 真なら返り値はUint8Array, 偽なら文字列
@return: 配列か文字列
b7utf8.decode(In)
@In: 文字列
@return: 要素が0~127の数値配列
*/
const b7utf8={
encode(In,toB){
const z=In.length,E=Uint8Array.from({13:1,36:2,92:3,96:4,length:97}),Out=new Uint8Array(E[In[z-1]]?E[In[z-2]]?E[In[z-3]]?z+1:z:z+1:z);
for(let a=0,o=0,n,e;a<z;Out[o++]=n)
//特殊文字の処理
if(e=E[n=In[a++]])
n=e<<7|In[a++]&127,//2つのbyteを1つに圧縮
Out[o++]=n>>6|192,n=n&63|128;//utf8 2byte文字
return toB?Out:new TextDecoder().decode(Out)
},
decode(In){
const z=In.length,E=new Uint8Array([,13,36,92,96]),Out=[];
for(let a=0,o=0,n,e=-1;a<z||~e;e=e?n&127:-1)
n=~e?e:In.charCodeAt(a++),Out[o++]=E[e=n>>7]||n;
return Out
},
//上記との違いは\0も変換する点. こっちの方が使いやすい(decodeも同様)
encode2(In,toB){
const z=In.length,E=Uint8Array.from({0:1,13:2,36:3,92:4,96:5,length:97}),Out=new Uint8Array(E[In[z-1]]?E[In[z-2]]?E[In[z-3]]?z+1:z:z+1:z);
for(let a=0,o=0,n,e;a<z;Out[o++]=n)
if(e=E[n=In[a++]])
n=e<<7|In[a++]&127,
Out[o++]=n>>6|192,n=n&63|128;
return toB?Out:new TextDecoder().decode(Out)
},
decode2(In){
const z=In.length,E=[,0,13,36,92,96],Out=[];
for(let a=0,o=0,n,e=-1;a<z||~e;e=e?n&127:-1)
n=~e?e:In.charCodeAt(a++),Out[o++]=E[e=n>>7]??n;
return Out
}}
例
const input=[0,1,13,36,92,96,111,127];
let e=b7utf8.encode(input);//符号化
let d=b7utf8.decode(e);//復号
console.log(e,d);
//\0も変換する場合
let e2=b7utf8.encode2(input);//符号化
let d2=b7utf8.decode2(e2);//復号
console.log(e2,d2)