JavaScript
encoding
UTF-8
unicode
encoding.js

javascriptで文字コード変換

More than 1 year has passed since last update.


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等への対応が出来ないのが残念。