javascriptで文字コード変換

  • 177
    Like
  • 2
    Comment

javascriptで文字コード変換

概要

javascriptの文字列はUTF-16で String#charCodeAt(i) で取得出来る数値は2byte(0x0000から0xffff)になる。 escape("あ") は UTF-16がそのままエスケープされ "%u3042" になるが encodeURI("あ"),encodeURIComponent("あ") などではUTF-8に変換されて "%E3%81%82" になる。

var str = "文字列をUTF-8に";
var utf8str = unescape(encodeURIComponent(str));
var utf16str = decodeURIComponent(escape(utf8str));
if (str == utf16str) {alert(true);} //=> true

とすることで UTF-16<=>UTF-8 の変換は出来るが、その他の文字コードは変換テーブルを用意する必要がある。 Shift_JIS(MS932), EUC-JP, UTF-8, JIS(iso-2022-jp)に対応したライブラリを2つ見つけたので検証する。

また、検証を進める中でecl.jsのJIS漢字テーブルを使った文字コード変換ライブラリを自作してしまったのでこれも合わせて検証する。文字列を一度数値配列にしてから変換するのでecl_array.jsとした。

(2014-09-19追記)さらに

というライブラリについて検証を追加した

検証

各ライブラリの特徴

ecl.jsは各文字コードへURLエスケープする為のライブラリであって、直接文字コード変換する機能はない。 よって UNICODE(Javascript文字列) から SJIS へ変換する場合は

var str = "UNICODE文字列をシフトJISへ";
var sjis = UnescapeUnicode(EscapeSJIS(str));
var str2 = UnescapeSJIS(EscapeUnicode(sjis));
if (str==str2){alert("true");} //=> true

のようにする必要がある。

Javascript文字列→SJISに変換してURLエスケープ→URLアンエスケープ(SJIS文字列)

このエスケープ・アンエスケープ処理を挟んでいる分ecl.jsは不利である。

encoding.jsの文字コード変換は配列を渡し配列で返るので文字列から配列に変換する関数は別途用意する必要がある。

// 文字列を配列に変換
var str2array = function(str) {
    var array = [],i,il=str.length;
    for(i=0;i<il;i++) array.push(str.charCodeAt(i));
    return array;
};
var str   = "あいうえお",
        array = str2array( str ),
        sjis_array = Encoding.convert(array, "SJIS", "UNICODE"),
        sjis       = Encoding.codeToString( sjis_array ); // 配列から文字列変換する関数は用意されている
alert(sjis);

text-encodingは日本語以外の様々なエンコーディングにも対応している。Encoding Living Standard( https://encoding.spec.whatwg.org/ )に沿った実装がされているようだ。以下のように使う。

var encoder = new TextEncoder("utf-8"),    // utf-8のエンコーダ(文字列→Uint8Array変換)を作成
    u8array = encoder.encode("あいうえお"), // => Uint8Array[240, 128, 128, 128, 240, 128, 128, 128, 240, 128, 128, 128, 240, 128, 128, 128, 240, 128, 128, 128]
    decoder = new TextDecoder("utf-8"),    // utf-8のデコーダ(Uint8Array→文字列)を作成
    decodedText = decoder.decode(u8array);  // => "あいうえお"
alert(decodedText);

//utf-8,utf-16以外のencodeにはオプションが必要
new TextEncoder("sjis", {NONSTANDARD_allowLegacyEncoding:true}).encode("ほげ"); // => Uint8Array[130, 216, 130, 176]
//decodeはOK
new TextDecoder("euc-jp").decode(new Uint8Array([164,226,164,178])); // => "もげ"

ちなみにTextEncoderおよびTextDecoderクラスはFirefoxでは実装済みなのでtext-encodingは不要。
ただしFirefoxのTextEncoderはエンコーディングにutf-8,utf-16le,utf-16be以外を指定するとエラーになる。
詳しくはMDNを参照 https://developer.mozilla.org/ja/docs/Web/API/TextEncoder

ファイルサイズはecl.jsが22KBとコンパクトなのに対し, encoding.jsは262KB js minifyしても212KBと少し大きい。
text-encodingは対応する文字コードが多いのでサイズも大きく600KB以上。
ecl.jsのJIS漢字テーブル(JCT11280)は独特な圧縮がされている上、全体的にサイズを小さくしようと凝縮された書き方がされている。はっきり言うと読みにくい、がjavascriptの文法の勉強になった。
encoding.jsはUTF8-JISの変換・逆変換テーブルがありこれが数値の配列で書かれているのでサイズが大きいがとてもわかりやすい。

自作のecl.jsベースの文字コード変換ライブラリecl_array.jsは、ecl.jsが1つのエスケープ関数で行っている処理(javascript文字列=Unicodeから各文字コードへ変換しエスケープする)をインターセプトして数値の配列で取り出すように設計した。文字コードはecl.jsで扱えるSJIS,EUCJP,JIS7,JIS8,UTF8,UTF7,UTF16LEに加えUTF16BE,MUTF7(modified utf-7),Base64を追加した。

var str = "あいうえお";
// 文字列を16bit数値の列に変換
var array = ECL.charset.Unicode.parse(str);   //=> [12354, 12356, 12358, 12360, 12362]
ECL.convert_array(array, "SJIS");    //=> [130, 160, 130, 162, 130, 164, 130, 166, 130, 168]
ECL.convert_array(array, "EUCJP");   //=> [164, 162, 164, 164, 164, 166, 164, 168, 164, 170]
// 配列からJS文字列(Unicode)に戻す場合は ECL.charset.Unicode.stringify( array )
ECL.charset.Unicode.stringify( array ); //=> "あいうえお"

// UTF-8文字列からSJIS文字列へ
var utf8str = unescape(encodeURIComponent("あいうえお"));
var sjisstr = ECL.convert(utf8str, 'SJIS', 'UTF8');
sjisstr == "\x82\xA0\x82\xA2\x82\xA4\x82\xA6\x82\xA8"; //=> true


/*
 例: ecl_array.js と CryptoJS を使いSJISテキストの暗号文・Base64・SHA1ハッシュを作る
    CryptoJS : https://code.google.com/p/crypto-js/
*/
var msg = "あいうえお";
var sjis = ECL.convert(msg, 'SJIS');
var sjis_wordarray = CryptoJS.enc.Latin1.parse( sjis ); // CryptoJSではWordArrayオブジェクトを作りデータをやりとりするのが楽
var encrypted = CryptoJS.AES.encrypt(sjis_wordarray, "Secret Passphrase"); //=> [object]
var base64 = sjis_wordarray.toString(CryptoJS.enc.Base64); //=> "gqCCooKkgqaCqA=="
var sha1 = CryptoJS.SHA1(sjis_wordarray).toString(CryptoJS.enc.Hex); //=> "2c332e42c55ba9293827672e85f16f1cda65518e"

実験

4KBのテキストを1000回文字コード変換した時の処理秒数(ms) U : UNICODE, S : SJIS, 8 : UTF8, E : EUC-JP, J : JIS

ライブラリ U->U U->S U->8 U->E U->J
encoding.js 495 336 507 503
ecl.js 1551 1483 1549 1756
ecl_array.js 616 289 599 624
native 344
ライブラリ S->U S->S S->8 S->E S->J
encoding.js 278 203 214 229
ecl.js 1516 2860 2829 3213
ecl_array.js 294 310 624 661
ライブラリ 8->U 8->S 8->8 8->E 8->J
encoding.js 195 338 347 345
ecl.js 1810 3329 3269 3298
ecl_array.js 314 616 607 638
native 323
ライブラリ E->U E->S E->8 E->E E->J
encoding.js 255 223 213 230
ecl.js 2444 3826 3741 4008
ecl_array.js 288 609 304 634
ライブラリ J->U J->S J->8 J->E J->J
encoding.js 277 217 247 209
ecl.js 1786 3165 2981 3111
ecl_array.js 547 902 616 853
  • nativeは unescape(encodeURIComponent(str))とdecodeURIComponent(escape(utf8str))のこと
  • 同文字コードへの変換(無変換)はスルーしているので変換速度には影響しない。
  • ほぼ全てにおいて encoding.js > ecl_array.js > ecl.js の順に速いという結果になった。
  • ecl.js はURLエスケープ、アンエスケープの影響で3~10倍程度遅い
  • ecl_array.js は UNICODEが絡む変換はそこそこ速いが、他はencoding.jsより2倍程度遅い。
  • encoding.jsは各文字コード間を直接変換しているが、ecl_array.jsは一度UNICODE配列にしているのでUNICODEが絡まない変換が遅いと思われる。
  • (2014-03-12追記) その後ecl_array.jsに手を入れ変換速度はencoding.jsと同程度になった
  • (2014-09-19追記) text-encodingは未検証。少し試した限りではかなり(50倍ほど)遅かった。

4KBのテキストを1000回文字変換+URLエスケープ/アンエスケープした時の処理秒数(ms)

ライブラリ esc U esc S esc 8 esc E esc J unesc U unesc S unesc 8 unesc E unesc J
encoding.js 180 1524 1512 1194 1227 287 406 419 388 716
ecl.js 689 1133 1389 1123 1366 461 649 948 1656 964
ecl_array.js 476 934 775 949 940 248 281 313 286 575
native 118 306
  • JS文字列→UTF-8エスケープならばnativeが最速
  • ecl.js のエスケープは遅い。ecl_array.js はエスケープ・アンエスケープ処理を変更したら速くなった(正規表現による置換をやめた)
  • encoding.jsもアンエスケープは速い。ただUNICODEエスケープは内部でencodeURIComponentを使用しているためUTF-8へ変換されている

考察

text-encodingはサイズが大きく変換も遅いが対応する文字コードが多く便利。
日本語の文字コード変換をしたい場合は encoding.js か ecl_array.js を使うのがよいだろう。
Firefox19以降限定なら外部ライブラリなしでTextEncoder/TextDecoderが使える。

ecl.jsは昔からある有名なライブラリだがこのコンパクトさが素晴らしい。JIS漢字11280字34KB程をASCII印字可能文字とデコーダ含めて13KBに納めている。エンコーダが無くJIS X 0213等への対応が出来ないのが残念。