#はじめに
URLパラメータ(クエリ文字列)は、通常、パーセントエンコーディングします。
ただ、URLに本来使用できない文字(漢字、カナ、一部の記号など)をエンコードすると、かなり長い文字列にエンコードされてしまうのが不便に感じることがありますね。
例えば、"本日は晴天なり。"をパーセントエンコーディングすると、"%E6%9C%AC%E6%97%A5%E3%81%AF%E6%99%B4%E5%A4%A9%E3%81%AA%E3%82%8A%E3%80%82"と、結構長くなるんですね。
私は、とても長い文字列をURLパラメータで渡したいアプリケーションを開発しているのですが、勢いで、自分で文字列の圧縮エンコードのJavaScriptライブラリを作ってしまいました。
ところが、ライブラリを作った後で調べて分かったのですが、lz-stringという、文字列圧縮用の有名なライブラリが、すでにあったみたいなんです。私は車輪の再発明をしてしまったのかもしれません。
この記事では、最初にlz-stringの紹介をして、次に私の作ったURLCompressorライブラリの紹介をします。
#lz-stringについて
lz-stringは、ローカルストレージの容量を有効活用するために開発された、JavaScriptの文字列圧縮ライブラリの様です。lz-stringは、C++、C#、Java、PHP、Pythonなど、色々な言語に移植されています。
ローカルストレージは、キーに関連付けした形でデータの文字列をブラウザに保存できるのですが、容量が5MBしかありません。(それでもクッキーと比較したら広大ですが) ローカルストレージに効率よく情報を保存しようとすれば、文字列の圧縮が必要になります。
また、ローカルストレージはJavaScript標準のUTF-16形式の文字列を保存するので、ASCIIコードやUTF-8など、ビット数の少ない文字コードを使うと、上位ビットを0のまま残してしまい、ストレージの利用効率が悪くなってしまうという性質があります。
そこで、lz-stringでは、UTF-16表現の文字列の形に圧縮できる様になっています。それに加えて、6ビット、7ビット、8ビットなどの文字コードにも圧縮できる様になっています。
lz-stringをURLパラメータに使う場合は、URLセーフな64種類の文字で圧縮する、LZString.compressToEncodedURIComponent関数を使います。
例えば、"本日は晴天なり。"を圧縮する場合は、次の様なコードになります。
// lz-stringを読み込む(Node.jsの場合は必要。ブラウザで使う場合は不要)
const LZString=require('./lz-string.js');
// "本日は晴天なり。"をURLセーフな文字列に圧縮する
const str = LZString.compressToEncodedURIComponent('本日は晴天なり。');
// 圧縮結果の表示
console.log(str); // "jTmlPTT2DILmaJSahWDIUQyCAGIA"と表示される
圧縮した文字列をデコードするには、LZString.decompressFromEncodedURIComponent関数を使います。
先程"本日は晴天なり。"を圧縮して得られた"jTmlPTT2DILmaJSahWDIUQyCAGIA"という文字列をデコードする場合は、次の様なコードになります。
// lz-stringを読み込む(Node.jsの場合は必要。ブラウザで使う場合は不要)
const LZString=require('./lz-string.js');
// "jTmlPTT2DILmaJSahWDIUQyCAGIA"をデコードする
const str = LZString.decompressFromEncodedURIComponent('jTmlPTT2DILmaJSahWDIUQyCAGIA');
// デコード結果の表示
console.log(str); // "本日は晴天なり。"と表示される
#URLCompressorについて
lz-stringという素晴らしいライブラリがすでにあるのを知らずに、私が開発したのがURLCompressorという文字列圧縮ライブラリです。
圧縮結果をURLセーフな文字列にも、8ビットバイナリにもできるのですが、ここではURLセーフな文字列に関してだけ、使い方を説明します。
文字列をURLセーフな文字列に圧縮するには、URLCompressor.compress関数を使います。
例えば、"本日は晴天なり。"を圧縮する場合は、次の様なコードになります。
// URLCompressorを読み込む(Node.jsの場合は必要。ブラウザで使う場合は不要)
const URLCompressor=require('./url-comp.js');
// "本日は晴天なり。"をURLセーフな文字列に圧縮する
const str=URLCompressor.compress('本日は晴天なり。');
// 圧縮結果の表示
console.log(str); // "DiX8ReaZzrfpP4jxuH4JVY0ne47QHoZ"と表示される
URLCompressor.compress関数で圧縮した文字列をデコードするには、URLCompressor.expand関数を使います。
先程"本日は晴天なり。"を圧縮して得られた"DiX8ReaZzrfpP4jxuH4JVY0ne47QHoZ"という文字列をデコードする場合は、次の様なコードになります。
// URLCompressorを読み込む(Node.jsの場合は必要。ブラウザで使う場合は不要)
const URLCompressor=require('./url-comp.js');
// "DiX8ReaZzrfpP4jxuH4JVY0ne47QHoZ"をデコードする
const str=URLCompressor.expand('DiX8ReaZzrfpP4jxuH4JVY0ne47QHoZ');
// デコード結果の表示
console.log(str); // "本日は晴天なり。"と表示される
#色々な文字列をlz-stringとURLCompressorでエンコードして比較する
lz-stringとUrlCompressorを比較するために、色々な文字列をlz-stringとURLCompressorで圧縮してみました。なお使用したlz-stringのバージョンは1.3.4で、URLCompressorのバージョンは0.1.0です。
短い英単語を圧縮する
英単語の例として、"apple"を圧縮すると、次の様な結果になりました。
- lz-string: "IYBxBsFMg" (9バイト)
- URLCompressor: "wx-ygz" (6バイト)
##漢字カナ交じりの短文を圧縮する
先程も説明したとおり、"本日は晴天なり。"を圧縮すると、次の様な結果になりました。
- lz-string: "jTmlPTT2DILmaJSahWDIUQyCAGIA" (28バイト)
- URL-Compressor: "DiX8ReaZzrfpP4jxuH4JVY0ne47QHoZ" (31バイト)
##短いURLを圧縮する
URLの例として、"**https://www.yahoo.co.jp**"を圧縮すると、次の様な結果になりました。
- lz-string: "BYFxAcGcC4HpYO5IHQE8CGwD2XkGNcArcIA" (35バイト)
- URL-Compressor: "zJO5BmGTFiQT59WR_UznMkM0" (24バイト)
##JSONを圧縮する
JSONの例として、次の様な229バイトのデータを圧縮してみました。
{
"date":"2021/07/01",
"item":[
{
"id":"636-7202",
"name":"apple",
"price":153,
"number":5
},
{
"id":"285-9910",
"name":"orange",
"price":78,
"number":10
}
]
}
圧縮関数に文字列を渡す時には、次のような文字列を渡しています。
'{\n "date":"2021/07/01",\n "item":[\n {\n "id":"636-7202",\n "name":"apple",\n "price":153,\n "number":5\n },\n {\n "id":"285-9910",\n "name":"orange",\n "price":78,\n "number":10\n }\n ]\n}'
実際にどの様な文字列にエンコードされたかはあまり興味がないでしょうから、圧縮後のバイト数だけ書くと、次の様になりました。
- lz-string: 196バイト
- URLCompressor: 140バイト
なお、スペースを取って、次の様にしたJSONファイル(143バイト)でも試してみました。
{"date":"2021/07/01","item":[{"id":"636-7202","name":"apple","price":153,"number":5},{"id":"285-9910","name":"orange","price":78,"number":10}]}
圧縮結果は次の様になりました。
- lz-string: 160バイト
- URLCompressor: 114バイト
##日本語の長文を圧縮する
日本語の長文の例として、夏目漱石の「坊ちゃん」の最初の段落を圧縮してみました。237文字で、UTF-8表現すれば711バイトあります。
親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。
この文章を圧縮した結果は次の様になりました。
- lz-string: 532バイト
- URLCompressor: 545バイト
##比較まとめ
lz-stringとURLCompressorの圧縮効率を比較しましたが、どうやら1バイト文字だけの場合は、URLCompressorの方が少ないバイト数に圧縮でき、漢字などのマルチバイト文字が多く入るとlz-stringの方が短い文字に圧縮できる傾向があるみたいです。URLCompressorがマルチバイト文字を圧縮する際は、一旦UTF-8にエンコードし、全ての文字コードを8ビットにしてから圧縮しているのですが、これがマルチバイト文字の圧縮効率を下げている気がします。
今回は調べませんでしたが、lz-stringは圧縮速度が速いのを売り文句にしているみたいです。URLCompressorは特に高速化を意識して作っていませんから、この点ではlz-stringに負けていそうな気がします。
私がなぜURLCompressorを作ったかというと、BASICで作ったプログラムをURLに埋め込みたかったからですが、BASICのプログラムに限定して比較すると、URLCompressorはlz-stringに対して、圧縮率の点でアドバンテージがあります。わざわざ作った甲斐が、かろうじてあったような気がします。
#URLCompressorの今後
URLCompressorは、現状ではJavaScriptでしか動いていませんから、PHPに移植して、サーバーとクライアントの間のデータの受け渡しに使える様にしたいと思っています。
データ圧縮のアルゴリズムに関しては、JavaScriptのコードを小さくする事を優先して、以前作ったSafe Melt 32というアーカイバと比較すると、かなり簡略化したアルゴリズムを採用しました。時間が取れれば、さらに本格的なアルゴリズムを実装して、約2KバイトのURLに、どれだけの情報を詰め込めるかに挑戦してみたいです。