JavaScript 文字列をArrayBuffersに、またはその逆に効率的に変換するための一般的に受け入れられている手法はありますか? localStorage
具体的には、ArrayBuffer の内容を書き込んで読み戻せるようにしたいと考えています。
27 に答える
Blob / FileReaderを使用するDennisとgengkevのソリューションは機能しますが、そのアプローチを取ることはお勧めしません。これは単純な問題への非同期アプローチであり、直接的な解決策よりもはるかに低速です。よりシンプルで(はるかに高速な)ソリューションを使用してhtml5rocksに投稿しました:http: //updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
そして解決策は次のとおりです。
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
編集:
Encoding APIは、文字列変換の問題を解決するのに役立ちます。上記の元の記事に対するHtml5Rocks.comのJeffPosnikからの応答を確認してください。
抜粋:
Encoding APIを使用すると、多くの標準エンコーディングのどれを使用する必要があるかに関係なく、生のバイトとネイティブJavaScript文字列の間の変換が簡単になります。
<pre id="results"></pre>
<script>
if ('TextDecoder' in window) {
// The local files to be fetched, mapped to the encoding that they're using.
var filesToEncoding = {
'utf8.bin': 'utf-8',
'utf16le.bin': 'utf-16le',
'macintosh.bin': 'macintosh'
};
Object.keys(filesToEncoding).forEach(function(file) {
fetchAndDecode(file, filesToEncoding[file]);
});
} else {
document.querySelector('#results').textContent = 'Your browser does not support the Encoding API.'
}
// Use XHR to fetch `file` and interpret its contents as being encoded with `encoding`.
function fetchAndDecode(file, encoding) {
var xhr = new XMLHttpRequest();
xhr.open('GET', file);
// Using 'arraybuffer' as the responseType ensures that the raw data is returned,
// rather than letting XMLHttpRequest decode the data first.
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
if (this.status == 200) {
// The decode() method takes a DataView as a parameter, which is a wrapper on top of the ArrayBuffer.
var dataView = new DataView(this.response);
// The TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
var decoder = new TextDecoder(encoding);
var decodedString = decoder.decode(dataView);
// Add the decoded file's text to the <pre> element on the page.
document.querySelector('#results').textContent += decodedString + '\n';
} else {
console.error('Error while requesting', file, this);
}
};
xhr.send();
}
</script>
stringencodingライブラリによってポリフィルされたEncoding standardを使用TextEncoder
して、ArrayBuffers との間で文字列を変換できます。TextDecoder
var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Blob はString.fromCharCode(null,array);
しかし、配列バッファが大きくなりすぎると失敗します。私が見つけた最良の解決策は、それを使用String.fromCharCode(null,array);
して、スタックを爆破しない操作に分割することですが、一度に1文字よりも高速です。
大きな配列バッファーの最適な解決策は次のとおりです。
function arrayBufferToString(buffer){
var bufView = new Uint16Array(buffer);
var length = bufView.length;
var result = '';
var addition = Math.pow(2,16)-1;
for(var i = 0;i<length;i+=addition){
if(i + addition > length){
addition = length - i;
}
result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
}
return result;
}
これは、blob を使用するよりも約 20 倍高速であることがわかりました。100MB を超える大きな文字列でも機能します。
BlobBuilderは String と ArrayBuffer を処理できるため、 gengkevの回答に基づいて、両方の方法で関数を作成しました。
function string2ArrayBuffer(string, callback) {
var bb = new BlobBuilder();
bb.append(string);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result);
}
f.readAsArrayBuffer(bb.getBlob());
}
と
function arrayBuffer2String(buf, callback) {
var bb = new BlobBuilder();
bb.append(buf);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result)
}
f.readAsText(bb.getBlob());
}
簡単なテスト:
string2ArrayBuffer("abc",
function (buf) {
var uInt8 = new Uint8Array(buf);
console.log(uInt8); // Returns `Uint8Array { 0=97, 1=98, 2=99}`
arrayBuffer2String(buf,
function (string) {
console.log(string); // returns "abc"
}
)
}
)
以下はすべて、配列バッファーからバイナリ文字列を取得することに関するものです
使用しないことをお勧めします
var binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));
なぜなら
- 大きなバッファーでクラッシュします (誰かが 246300 の「魔法の」サイズについて書きましたが
Maximum call stack size exceeded
、120000 バイトのバッファーでエラーが発生しました (Chrome 29)) - パフォーマンスが非常に悪い(以下を参照)
同期ソリューションが正確に必要な場合は、次のようなものを使用します
var
binaryString = '',
bytes = new Uint8Array(arrayBuffer),
length = bytes.length;
for (var i = 0; i < length; i++) {
binaryString += String.fromCharCode(bytes[i]);
}
前のものと同じくらい遅いですが、正しく動作します。これを書いている時点では、その問題に対する非常に高速な同期ソリューションはないようです (このトピックで言及されているすべてのライブラリは、同期機能に対して同じアプローチを使用しています)。
しかし、私が本当にお勧めするのは、Blob
+FileReader
アプローチを使用することです
function readBinaryStringFromArrayBuffer (arrayBuffer, onSuccess, onFail) {
var reader = new FileReader();
reader.onload = function (event) {
onSuccess(event.target.result);
};
reader.onerror = function (event) {
onFail(event.target.error);
};
reader.readAsBinaryString(new Blob([ arrayBuffer ],
{ type: 'application/octet-stream' }));
}
唯一の欠点 (すべてではない) は、それが非同期であることです。また、以前のソリューションよりも約8 ~ 10 倍高速です。(詳細: 私の環境での同期ソリューションでは、2.4Mb バッファーで 950 ~ 1050 ミリ秒かかりましたが、FileReader を使用したソリューションでは、同じ量のデータで約 100 ~ 120 ミリ秒かかりました。また、100Kb バッファーで両方の同期ソリューションをテストしたところ、ほぼ同じ時間なので、「適用」を使用してもループはそれほど遅くはありません。)
ところで、ここで: ArrayBuffer を String との間で変換する方法著者は、私のような 2 つのアプローチを比較し、完全に反対の結果を得ています (彼のテスト コードは here です) なぜそんなに異なる結果になるのでしょうか? おそらく、1Kb の長さのテスト文字列が原因です (彼はそれを「veryLongStr」と呼んでいました)。私のバッファは、サイズが 2.4Mb の非常に大きな JPEG 画像でした。
(更新この回答の後半を参照してください。ここで、(うまくいけば)より完全なソリューションを提供しています。)
私もこの問題に遭遇しました.FF 6で次のように動作します(一方向):
var buf = new ArrayBuffer( 10 );
var view = new Uint8Array( buf );
view[ 3 ] = 4;
alert(Array.prototype.slice.call(view).join(""));
残念ながら、もちろん、文字ではなく、配列内の値の ASCII テキスト表現になってしまいます。ただし、ループよりもはるかに効率的です (はずです)。例えば。上記の例では、結果は0004000000
、複数のヌル文字と chr(4) ではなく、 です。
編集:
ここでMDC を調べた後、次のように からを作成できます。ArrayBuffer
Array
var arr = new Array(23);
// New Uint8Array() converts the Array elements
// to Uint8s & creates a new ArrayBuffer
// to store them in & a corresponding view.
// To get at the generated ArrayBuffer,
// you can then access it as below, with the .buffer property
var buf = new Uint8Array( arr ).buffer;
元の質問に答えるために、これによりArrayBuffer
<->String
を次のように変換できます。
var buf, view, str;
buf = new ArrayBuffer( 256 );
view = new Uint8Array( buf );
view[ 0 ] = 7; // Some dummy values
view[ 2 ] = 4;
// ...
// 1. Buffer -> String (as byte array "list")
str = bufferToString(buf);
alert(str); // Alerts "7,0,4,..."
// 1. String (as byte array) -> Buffer
buf = stringToBuffer(str);
alert(new Uint8Array( buf )[ 2 ]); // Alerts "4"
// Converts any ArrayBuffer to a string
// (a comma-separated list of ASCII ordinals,
// NOT a string of characters from the ordinals
// in the buffer elements)
function bufferToString( buf ) {
var view = new Uint8Array( buf );
return Array.prototype.join.call(view, ",");
}
// Converts a comma-separated ASCII ordinal string list
// back to an ArrayBuffer (see note for bufferToString())
function stringToBuffer( str ) {
var arr = str.split(",")
, view = new Uint8Array( arr );
return view.buffer;
}
便宜上、function
生の UnicodeString
をに変換するための を次に示しますArrayBuffer
(ASCII/1 バイト文字でのみ機能します)。
function rawStringToBuffer( str ) {
var idx, len = str.length, arr = new Array( len );
for ( idx = 0 ; idx < len ; ++idx ) {
arr[ idx ] = str.charCodeAt(idx) & 0xFF;
}
// You may create an ArrayBuffer from a standard array (of values) as follows:
return new Uint8Array( arr ).buffer;
}
// Alerts "97"
alert(new Uint8Array( rawStringToBuffer("abc") )[ 0 ]);
上記により、ArrayBuffer
-> String
&からArrayBuffer
、文字列が格納されている場所に戻ることができます。.localStorage
:)
お役に立てれば、
ダン
さて、これは同じことをするためのやや複雑な方法です:
var string = "Blah blah blah", output;
var bb = new (window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder)();
bb.append(string);
var f = new FileReader();
f.onload = function(e) {
// do whatever
output = e.target.result;
}
f.readAsArrayBuffer(bb.getBlob());
編集: BlobBuilderは、私がこの投稿を最初に書いたときには存在しなかったBlobコンストラクターを支持して、長い間非推奨になっています。これが更新されたバージョンです。(はい、これは常に変換を行うための非常にばかげた方法でしたが、それはただの楽しみのためでした!)
var string = "Blah blah blah", output;
var f = new FileReader();
f.onload = function(e) {
// do whatever
output = e.target.result;
};
f.readAsArrayBuffer(new Blob([string]));
ここを参照してください: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (JavaScript ArrayBuffer インターフェイスに基づく文字列用の C に似たインターフェイス)
atob() が返す「ネイティブ」バイナリ文字列は、1 文字あたり 1 バイトの配列です。
したがって、2 バイトを文字に格納するべきではありません。
var arrayBufferToString = function(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
var stringToArrayBuffer = function(str) {
return (new Uint8Array([].map.call(str,function(x){return x.charCodeAt(0)}))).buffer;
}
私はこれを使用し、私のために働きます。
function arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array( len );
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
BlobBuilder のような非推奨の API は使用しないことをお勧めします
BlobBuilder は、Blob オブジェクトによって長い間廃止されてきました。Dennis の回答のコード (BlobBuilder が使用されている場所) を以下のコードと比較してください。
function arrayBufferGen(str, cb) {
var b = new Blob([str]);
var f = new FileReader();
f.onload = function(e) {
cb(e.target.result);
}
f.readAsArrayBuffer(b);
}
非推奨の方法と比較して、これがどれほどクリーンで肥大化していないかに注意してください...ええ、これは間違いなくここで考慮すべきことです.
エムスクリプトン より:
function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) {
if (!(maxBytesToWrite > 0)) return 0;
var startIdx = outIdx;
var endIdx = outIdx + maxBytesToWrite - 1;
for (var i = 0; i < str.length; ++i) {
var u = str.charCodeAt(i);
if (u >= 55296 && u <= 57343) {
var u1 = str.charCodeAt(++i);
u = 65536 + ((u & 1023) << 10) | u1 & 1023
}
if (u <= 127) {
if (outIdx >= endIdx) break;
outU8Array[outIdx++] = u
} else if (u <= 2047) {
if (outIdx + 1 >= endIdx) break;
outU8Array[outIdx++] = 192 | u >> 6;
outU8Array[outIdx++] = 128 | u & 63
} else if (u <= 65535) {
if (outIdx + 2 >= endIdx) break;
outU8Array[outIdx++] = 224 | u >> 12;
outU8Array[outIdx++] = 128 | u >> 6 & 63;
outU8Array[outIdx++] = 128 | u & 63
} else {
if (outIdx + 3 >= endIdx) break;
outU8Array[outIdx++] = 240 | u >> 18;
outU8Array[outIdx++] = 128 | u >> 12 & 63;
outU8Array[outIdx++] = 128 | u >> 6 & 63;
outU8Array[outIdx++] = 128 | u & 63
}
}
outU8Array[outIdx] = 0;
return outIdx - startIdx
}
次のように使用します。
stringToUTF8Array('abs', new Uint8Array(3), 0, 4);