Blobコンストラクタに文字列を渡すと自動的にUTF-8に変換されることを利用(この動作は仕様)。
ブラウザ内
function str2bytes(str, callback) {
var fr = new FileReader();
fr.onloadend = function() {
callback(fr.result);
};
fr.readAsArrayBuffer(new Blob([str]));
}
Worker内ではFileReaderSyncが使えるのでcallback関数はなくてもいい。
function str2bytes(str) {
return new FileReaderSync().readAsArrayBuffer(new Blob([str]));
}
BlobコンストラクタはIE10, Chrome, Firefoxでサポート。
Safariは?
Operaは12.02では未サポート
未サポートのブラウザでは昔ながらの方法で変換する必要がある(ググればたくさん出てくる)。
パフォーマンス的には短い文字列のときは昔ながらの方法のほうが速い。
長い文字列のときはBlobコンストラクタを使ったほうが速い。
追記:
一応JSでの実装を載せておきます。String -> Arrayライクにアクセスできるオブジェクトということで。
昔ながらの実装
function unicode2utf8_array(str){
var n = str.length,
idx = -1,
bytes = [],
i, j, c;
for(i = 0; i < n; ++i){
c = str.charCodeAt(i);
if(c <= 0x7F){
bytes[++idx] = c;
} else if(c <= 0x7FF){
bytes[++idx] = 0xC0 | (c >>> 6);
bytes[++idx] = 0x80 | (c & 0x3F);
} else if(c <= 0xFFFF){
bytes[++idx] = 0xE0 | (c >>> 12);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
} else {
bytes[++idx] = 0xF0 | (c >>> 18);
bytes[++idx] = 0x80 | ((c >>> 12) & 0x3F);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
}
}
return bytes;
}
TypedArrayを使った高速な実装
function unicode2utf8_uint8array(str){
var n = str.length,
idx = -1,
byteLength = 512,
bytes = new Uint8Array(byteLength),
i, c, _bytes;
for(i = 0; i < n; ++i){
c = str.charCodeAt(i);
if(c <= 0x7F){
bytes[++idx] = c;
} else if(c <= 0x7FF){
bytes[++idx] = 0xC0 | (c >>> 6);
bytes[++idx] = 0x80 | (c & 0x3F);
} else if(c <= 0xFFFF){
bytes[++idx] = 0xE0 | (c >>> 12);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
} else {
bytes[++idx] = 0xF0 | (c >>> 18);
bytes[++idx] = 0x80 | ((c >>> 12) & 0x3F);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
}
//倍々でメモリを確保していく
if(byteLength - idx <= 4){
_bytes = bytes;
byteLength *= 2;
bytes = new Uint8Array(byteLength);
bytes.set(_bytes);
}
}
return bytes.subarray(0, ++idx);
}
パフォーマンス
結論:Uint8Arrayを使うとかなり速い。
追記2:
そういえばJavaScriptはシングルスレッドだったなぁっ(作業領域を共通化)てのと、コピーを返したほうが後々便利かなということで結果的に以下のような形に。
あともうひとつ、もしもtwitterのように文字列の最大の長さが決まっている場合は、領域を再確保する部分は省略しても構いません。とりあえず文字列の長さの4倍とっておけば大丈夫です。twitterの場合だったら var bytes = new Uint8Array(140 * 4);
とかですか。 if
一つが無いぶん速くなるでしょう。
var unicode2utf8_uint8array = (function(){
//作業用の領域を確保
var _bytes = new Uint8Array(512);
return function(str){
var n = str.length,
idx = -1,
bytes = _bytes, //ローカルに参照を渡す
byteLength = bytes.byteLength,
i, c;
for(i = 0; i < n; ++i){
c = str.charCodeAt(i);
if(c <= 0x7F){
bytes[++idx] = c;
} else if(c <= 0x7FF){
bytes[++idx] = 0xC0 | (c >>> 6);
bytes[++idx] = 0x80 | (c & 0x3F);
} else if(c <= 0xFFFF){
bytes[++idx] = 0xE0 | (c >>> 12);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
} else {
bytes[++idx] = 0xF0 | (c >>> 18);
bytes[++idx] = 0x80 | ((c >>> 12) & 0x3F);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
}
//倍々でメモリを確保していく
if(byteLength - idx <= 4){
byteLength *= 2;
_bytes = new Uint8Array(byteLength);
_bytes.set(bytes);
bytes = _bytes;
}
}
//コピーを返す
return new Uint8Array(bytes.subarray(0, ++idx));
};
}();