Konvertieren zwischen Strings und ArrayBuffers


264

Gibt es eine allgemein akzeptierte Technik zum effizienten Konvertieren von JavaScript-Zeichenfolgen in ArrayBuffers und umgekehrt? Insbesondere möchte ich in der Lage sein, den Inhalt eines ArrayBuffers in diesen zu schreiben localStorageund ihn zurückzulesen.


1
Ich habe keine Erfahrung damit, aber nach der API-Dokumentation ( khronos.org/registry/typedarray/specs/latest ) zu urteilen, wenn Sie eine erstellen, ist Int8Array ArrayBufferViewes möglicherweise möglich, einfach die Klammernotation zum Kopieren von Zeichen zu verwenden string[i] = buffer[i]und umgekehrt.
FK82

2
@ FK82, das sieht nach einem vernünftigen Ansatz aus (mit Uint16Arrays für die 16-Bit-Zeichen von JS), aber JavaScript-Zeichenfolgen sind unveränderlich, sodass Sie einer Zeichenposition nicht direkt zuweisen können. Ich würde immer noch brauchen zu kopieren String.fromCharCode(x)in der jeder Wert Uint16Arrayzu einem normalen Arrayund rufen Sie dann .join()auf die Array.
Kpozin

@kpozin: Stimmt, habe das nicht wirklich durchdacht.
FK82

5
@kpozin Es stellt sich heraus, dass die meisten modernen JS-Engines die String-Verkettung so weit optimiert haben, dass es billiger ist, sie nur zu verwenden string += String.fromCharCode(buffer[i]);. Es scheint seltsam, dass es keine integrierten Methoden zum Konvertieren zwischen Zeichenfolgen und typisierten Arrays gibt. Sie mussten wissen, dass so etwas auftauchen würde.
Download

arrayBuffer.toString () funktioniert gut für mich.
Bürger Conn

Antworten:


129

Update 2016 - Fünf Jahre später gibt es jetzt neue Methoden in den Spezifikationen (siehe Unterstützung unten), um zwischen Zeichenfolgen und typisierten Arrays unter Verwendung der richtigen Codierung zu konvertieren.

TextEncoder

Das TextEncoderstellt dar :

Die TextEncoderSchnittstelle stellt einen Codierer für eine bestimmte Methode dar, dh eine bestimmte Zeichenkodierung, wie z utf-8.iso-8859-2, koi8, cp1261, gbk, ... Ein Encoder nimmt einen Strom von Codepunkten als Eingabe und gibt einen Strom von Bytes aus.

Änderungsnotiz, da oben geschrieben wurde: (ibid.)

Hinweis: Firefox, Chrome und Opera unterstützten früher andere Codierungstypen als utf-8 (z. B. utf-16, iso-8859-2, koi8, cp1261 und gbk). Ab Firefox 48 [...], Chrome 54 [...] und Opera 41 sind außer utf-8 keine anderen Codierungstypen verfügbar, um der Spezifikation zu entsprechen. *

*) Aktualisierte Spezifikationen (W3) und hier (whatwg).

Nach dem Erstellen einer Instanz von TextEncoderwird eine Zeichenfolge verwendet und mit einem bestimmten Codierungsparameter codiert:

if (!("TextEncoder" in window)) 
  alert("Sorry, this browser does not support TextEncoder...");

var enc = new TextEncoder(); // always utf-8
console.log(enc.encode("This is a string converted to a Uint8Array"));

Sie verwenden dann natürlich den .bufferParameter für das ErgebnisUint8Array , um die Unterlage ArrayBufferbei Bedarf in eine andere Ansicht zu konvertieren .

Stellen Sie einfach sicher, dass die Zeichen in der Zeichenfolge dem Codierungsschema entsprechen. Wenn Sie beispielsweise im Beispiel Zeichen außerhalb des UTF-8-Bereichs verwenden, werden diese in zwei Bytes anstelle von einem codiert.

Für den allgemeinen Gebrauch würden Sie die UTF-16-Codierung für Dinge wie verwenden localStorage.

TextDecoder

Ebenso kann der umgekehrte Prozess verwendet dieTextDecoder :

Die TextDecoderSchnittstelle stellt einen Decoder für ein spezielles Verfahren, die eine spezifische Zeichencodierung ist, wie utf-8, iso-8859-2, koi8, cp1261, gbk, ... ein Decoder einen Strom von Bytes als Eingabe und gibt einen Strom von Codepunkten.

Alle verfügbaren Dekodierungsarten finden Sie hier .

if (!("TextDecoder" in window))
  alert("Sorry, this browser does not support TextDecoder...");

var enc = new TextDecoder("utf-8");
var arr = new Uint8Array([84,104,105,115,32,105,115,32,97,32,85,105,110,116,
                          56,65,114,114,97,121,32,99,111,110,118,101,114,116,
                          101,100,32,116,111,32,97,32,115,116,114,105,110,103]);
console.log(enc.decode(arr));

Die MDN StringView-Bibliothek

Eine Alternative zu diesen ist die Verwendung der StringViewBibliothek (lizenziert als lgpl-3.0). Das Ziel ist:

  • Erstellen einer C-ähnlichen Schnittstelle für Zeichenfolgen (dh eines Arrays von Zeichencodes - eine ArrayBufferView in JavaScript) basierend auf der JavaScript-ArrayBuffer-Schnittstelle
  • Erstellen einer hoch erweiterbaren Bibliothek, die jeder durch Hinzufügen von Methoden zum Objekt StringView.prototype erweitern kann
  • Erstellen einer Sammlung von Methoden für solche stringähnlichen Objekte (seitdem: stringViews), die ausschließlich mit Arrays von Zahlen arbeiten und nicht mit dem Erstellen neuer unveränderlicher JavaScript-Strings
  • um mit anderen Unicode-Codierungen als den Standard-UTF-16-DOMStrings von JavaScript zu arbeiten

viel mehr Flexibilität geben. Es würde jedoch erfordern, dass wir eine Verknüpfung zu dieser Bibliothek herstellen oder diese einbetten, während TextEncoder/ TextDecoderin modernen Browsern integriert ist.

Unterstützung

Stand Juli / 2018:

TextEncoder (Experimentell, auf Standardstrecke)

 Chrome    | Edge      | Firefox   | IE        | Opera     | Safari
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |    19°    |     -     |     25    |     -

 Chrome/A  | Edge/mob  | Firefox/A | Opera/A   |Safari/iOS | Webview/A
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |    19°    |     ?     |     -     |     38

°) 18: Firefox 18 implemented an earlier and slightly different version
of the specification.

WEB WORKER SUPPORT:

Experimental, On Standard Track

 Chrome    | Edge      | Firefox   | IE        | Opera     | Safari
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |     20    |     -     |     25    |     -

 Chrome/A  | Edge/mob  | Firefox/A | Opera/A   |Safari/iOS | Webview/A
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |     20    |     ?     |     -     |     38

Data from MDN - `npm i -g mdncomp` by epistemex

2
Keine Unterstützung für TextDecoder von IE & Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete


Keine Unterstützung für Safari Mobile (ios) am 18.04.2018: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
Bronze Man

Einzeiler: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};so können Sie nurvar array = encoder.encode('hello');
Yeti

1
Die Sache mit TextEncoderist, dass, wenn Sie Binärdaten in einer Zeichenfolge (wie Bild) haben, Sie nicht verwenden möchten TextEncoder(anscheinend). Zeichen mit Codepunkten größer als 127 erzeugen zwei Bytes. Warum habe ich Binärdaten in einer Zeichenfolge? cy.fixture(NAME, 'binary')( cypress) erzeugt einen String.
X-Yuri

175

Obwohl Dennis und Gengkev Lösungen für die Verwendung von Blob / FileReader funktionieren, würde ich diesen Ansatz nicht empfehlen. Es ist ein asynchroner Ansatz für ein einfaches Problem und viel langsamer als eine direkte Lösung. Ich habe einen Beitrag in html5rocks mit einer einfacheren und (viel schnelleren) Lösung verfasst: http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

Und die Lösung ist:

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;
}

BEARBEITEN:

Die Codierungs-API hilft bei der Lösung des Problems der Zeichenfolgenkonvertierung . Lesen Sie die Antwort von Jeff Posnik auf Html5Rocks.com auf den obigen Originalartikel.

Auszug:

Die Codierungs-API erleichtert die Übersetzung zwischen Rohbytes und nativen JavaScript-Zeichenfolgen, unabhängig davon, mit welcher der vielen Standardcodierungen Sie arbeiten müssen.

<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>

16
Leider ist mein Kommentar zu html5rocks noch nicht genehmigt. Daher hier eine kurze Antwort. Ich denke immer noch, dass dies nicht der richtige Weg ist, da Sie viele Zeichen vermissen, insbesondere weil die meisten Seiten heute in UTF-8-Codierung vorliegen. Auf der einen Seite gibt die charCodeAt-Funktion für weitere Sonderzeichen (sagen wir asiatisch) einen 4-Byte-Wert zurück, sodass sie zerhackt werden. Auf der anderen Seite vergrößern einfache englische Zeichen den ArrayBuffer zweimal (Sie verwenden 2 Byte für jedes 1-Byte-Zeichen). Stellen Sie sich vor, Sie senden einen englischen Text über ein WebSocket, der zweimal benötigt wird (in einer Echtzeitumgebung nicht gut).
Dennis

9
Drei Beispiele: (1) This is a cool text!20 Byte in UTF8 - 40 Byte in Unicode. (2) ÄÖÜ6 Bytes in UTF8 - 6 Bytes in Unicode. (3) ☐☑☒9 Bytes in UTF8 - 6 Bytes in Unicode. Wenn Sie die Zeichenfolge als UTF8-Datei speichern möchten (über Blob und File Writer API), können Sie diese beiden Methoden nicht verwenden, da sich der ArrayBuffer in Unicode und nicht in UTF8 befindet.
Dennis

3
Ich erhalte eine Fehlermeldung: Nicht erfasster RangeError: Maximale Aufrufstapelgröße überschritten. Was könnte das Problem sein?
Jacob

6
@Dennis - JS-Zeichenfolgen verwenden UCS2, nicht UTF8 (oder sogar UTF16) - was bedeutet, dass charCodeAt () immer die Werte 0 -> 65535 zurückgibt. Jeder UTF-8-Codepunkt, der 4-Byte-Enden erfordert, wird mit Ersatzpaaren dargestellt (siehe en.wikipedia .org / wiki /… ) - dh zwei separate 16-Bit-UCS2-Werte.
Broofa

6
@jacob - Ich glaube, der Fehler liegt darin, dass die Länge des Arrays, das an die apply () -Methode übergeben werden kann, begrenzt ist. ZB String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthfunktioniert für mich in Chrome, aber wenn Sie stattdessen 246301 verwenden, erhalte ich Ihre RangeError-Ausnahme
broofa

71

Sie können TextEncoderund TextDecoderaus dem Codierungsstandard , der von der Stringencodierungsbibliothek mehrfach gefüllt wird, verwenden, um Zeichenfolgen in und aus ArrayBuffers zu konvertieren:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

2
Dies ist übrigens standardmäßig in Firefox verfügbar: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard

2
Daumen hoch für neue APIs, die viel besser sind als seltsame Problemumgehungen!
Tomáš Zato - Wiedereinsetzung Monica

1
Dies funktioniert nicht mit allen Arten von Charakteren.
David

5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Nein Danke.
Evan Hu

murren ... wenn ich einen vorhandenen Array-Puffer habe, in den ich einen String schreiben möchte, muss ich wohl das uint8array nehmen und es ein zweites Mal kopieren?
Shaunc

40

Blob ist viel langsamer als String.fromCharCode(null,array);

Dies schlägt jedoch fehl, wenn der Array-Puffer zu groß wird. Die beste Lösung, die ich gefunden habe, besteht darin, sie zu verwenden String.fromCharCode(null,array);und in Vorgänge aufzuteilen, die den Stapel nicht sprengen, aber schneller als jeweils ein Zeichen sind.

Die beste Lösung für große Array-Puffer ist:

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;

}

Ich fand das ungefähr 20 Mal schneller als mit Blob. Es funktioniert auch für große Strings von über 100 MB.


3
Wir sollten mit dieser Lösung gehen. Da dies einen Anwendungsfall mehr löst als den akzeptierten
sam

24

Basierend auf der Antwort von gengkev habe ich Funktionen für beide Arten erstellt, da BlobBuilder String und ArrayBuffer verarbeiten kann:

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());
}

und

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());
}

Ein einfacher Test:

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"
            }
        )
    }
)

Wollten Sie in arrayBuffer2String () callback (...) anstelle von console.log () aufrufen? Andernfalls wird das Rückrufargument nicht verwendet.
Dan Phillimore

Dies scheint der richtige Weg zu sein - danke Genkev und Dennis. Scheint irgendwie albern, dass es keinen synchronen Weg gibt, dies zu erreichen, aber was können Sie tun ...
kpozin

JavaScript ist Single-Threaded. Daher ist der FileReader aus zwei Gründen asynchron: (1) Er blockiert nicht die Ausführung anderer JavaScript-Dateien beim Laden einer (großen) Datei (stellen Sie sich eine komplexere Anwendung vor) und (2) blockiert nicht die Benutzeroberfläche / den Browser (häufiges Problem) mit langer Ausführung von JS-Code). Viele APIs sind asynchron. Auch in XMLHttpRequest 2 wird die Synchronität entfernt.
Dennis

Ich hatte wirklich gehofft, dass dies für mich funktionieren würde, aber die Konvertierung von String zu ArrayBuffer funktioniert nicht zuverlässig. Ich erstelle einen ArrayBuffer mit 256 Werten und kann daraus einen String mit der Länge 256 machen. Wenn ich dann aber versuche, diesen wieder in einen ArrayBuffer umzuwandeln - abhängig vom Inhalt meines anfänglichen ArrayBuffers -, bekomme ich 376 Elemente heraus. Wenn Sie versuchen möchten, mein Problem zu reproduzieren, behandle ich meinen ArrayBuffer als 16x16-Raster in einem Uint8Array mit Werten, die wie a[y * w + x] = (x + y) / 2 * 16; versucht berechnet wurden getBlob("x"), mit vielen verschiedenen Mimetypen - kein Glück.
Matt Cruikshank

18
BlobBuilder ist in neueren Browsern veraltet. Wechseln Sie new BlobBuilder(); bb.append(buf);zu new Blob([buf]), wandeln Sie den ArrayBuffer in der zweiten Funktion über new UintArray(buf)(oder was auch immer für den zugrunde liegenden Datentyp geeignet ist) in ein UintArray um und entfernen Sie dann die getBlob()Aufrufe. Um die Sauberkeit zu gewährleisten, benennen Sie bb in blob um, da es kein BlobBuilder mehr ist.
Sowbug

18

Im Folgenden geht es darum, binäre Zeichenfolgen aus Array-Puffern abzurufen

Ich würde empfehlen, nicht zu verwenden

var binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));

weil es

  1. stürzt auf großen Puffern ab (jemand hat über die "magische" Größe von 246300 geschrieben, aber ich habe einen Maximum call stack size exceededFehler beim 120000-Byte-Puffer (Chrome 29) erhalten)
  2. es hat wirklich schlechte Leistung (siehe unten)

Wenn Sie genau eine synchrone Lösung benötigen, verwenden Sie so etwas wie

var
  binaryString = '',
  bytes = new Uint8Array(arrayBuffer),
  length = bytes.length;
for (var i = 0; i < length; i++) {
  binaryString += String.fromCharCode(bytes[i]);
}

Es ist so langsam wie das vorherige, funktioniert aber korrekt. Es scheint, dass es zum Zeitpunkt des Schreibens keine recht schnelle synchrone Lösung für dieses Problem gibt (alle in diesem Thema erwähnten Bibliotheken verwenden denselben Ansatz für ihre synchronen Funktionen).

Aber was ich wirklich empfehle, ist die Verwendung von Blob+ FileReaderAnsatz

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' }));
}

Der einzige Nachteil (nicht für alle) ist, dass es asynchron ist . Und es ist ungefähr 8-10 mal schneller als frühere Lösungen! (Einige Details: Die synchrone Lösung in meiner Umgebung benötigte 950-1050 ms für einen 2,4-MB-Puffer, aber die Lösung mit FileReader hatte Zeiten von etwa 100-120 ms für dieselbe Datenmenge. Und ich habe beide synchronen Lösungen auf einem 100-KB-Puffer getestet und sie haben gedauert fast zur gleichen Zeit, also ist die Schleife nicht viel langsamer als die Verwendung von 'anwenden'.)

Übrigens hier: Wie man ArrayBuffer in und von einem String- Autor konvertiert, vergleicht zwei Ansätze wie mich und erhält völlig entgegengesetzte Ergebnisse ( sein Testcode ist hier ). Warum so unterschiedliche Ergebnisse? Wahrscheinlich wegen seiner Testzeichenfolge, die 1 KB lang ist (er nannte sie "veryLongStr"). Mein Puffer war ein wirklich großes JPEG-Bild mit einer Größe von 2,4 MB.


13

( Update Bitte lesen Sie die 2. Hälfte dieser Antwort, in der ich (hoffentlich) eine vollständigere Lösung bereitgestellt habe.)

Ich bin auch auf dieses Problem gestoßen, das folgende funktioniert für mich in FF 6 (für eine Richtung):

var buf = new ArrayBuffer( 10 );
var view = new Uint8Array( buf );
view[ 3 ] = 4;
alert(Array.prototype.slice.call(view).join(""));

Leider erhalten Sie natürlich eher ASCII-Textdarstellungen der Werte im Array als Zeichen. Es ist (sollte) immer noch viel effizienter als eine Schleife. z.B. Für das obige Beispiel ist das Ergebnis 0004000000eher als mehrere Nullzeichen & ein chr (4).

Bearbeiten:

Nachdem Sie sich hier MDC angesehen haben , können Sie ein ArrayBufferaus einem Arrayder folgenden erstellen :

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;

Um Ihre ursprüngliche Frage zu beantworten, können Sie ArrayBuffer<-> Stringwie folgt konvertieren :

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;
}

Der Einfachheit halber ist hier ein functionzum Konvertieren eines rohen Unicodes Stringin einen ArrayBuffer(funktioniert nur mit ASCII / Ein-Byte-Zeichen)

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 ]);

Mit den obigen Anweisungen können Sie von ArrayBuffer-> String& zurück zu ArrayBuffererneut gehen, wo die Zeichenfolge in z. .localStorage:) :)

Hoffe das hilft,

Dan


1
Ich denke nicht, dass dies eine effiziente Methode ist (in Bezug auf Zeit oder Raum), und dies ist eine sehr ungewöhnliche Art, Binärdaten zu speichern.
Kpozin

@kpozin: Soweit ich weiß, gibt es keine andere Möglichkeit, Binärdaten in localStorage zu speichern
Dan Phillimore

1
Was ist mit der Base64-Codierung?
Nick Sotiros

13

Im Gegensatz zu den hier aufgeführten Lösungen musste ich in / von UTF-8-Daten konvertieren. Zu diesem Zweck habe ich die folgenden zwei Funktionen mit dem Trick (un) Escape / (en) decodeURIComponent codiert. Sie verschwenden ziemlich viel Speicher und weisen die 9-fache Länge des codierten utf8-Strings zu, obwohl diese von gc wiederhergestellt werden sollten. Verwenden Sie sie nur nicht für 100-MB-Text.

function utf8AbFromStr(str) {
    var strUtf8 = unescape(encodeURIComponent(str));
    var ab = new Uint8Array(strUtf8.length);
    for (var i = 0; i < strUtf8.length; i++) {
        ab[i] = strUtf8.charCodeAt(i);
    }
    return ab;
}

function strFromUtf8Ab(ab) {
    return decodeURIComponent(escape(String.fromCharCode.apply(null, ab)));
}

Überprüfen, ob es funktioniert:

strFromUtf8Ab(utf8AbFromStr('latinкирилицаαβγδεζηあいうえお'))
-> "latinкирилицаαβγδεζηあいうえお"

8

Falls Sie Binärdaten in einer Zeichenfolge haben (erhalten von nodejs+ readFile(..., 'binary')oder cypress+ cy.fixture(..., 'binary')usw.), können Sie diese nicht verwenden TextEncoder. Es unterstützt nur utf8. Bytes mit Werten >= 128werden jeweils in 2 Bytes umgewandelt.

ES2015:

a = Uint8Array.from(s, x => x.charCodeAt(0))

Uint8Array (33) [2, 134, 140, 186, 82, 70, 108, 182, 233, 40, 143, 247, 29, 76, 245, 206, 29, 87, 48, 160, 78, 225, 242 56, 236, 201, 80, 80, 152, 118, 92, 144, 48

s = String.fromCharCode.apply(null, a)

"ºRFl¶é (÷ LõÎW0 Náò8ìÉPPv \ 0"


7

Ich stellte fest, dass ich Probleme mit diesem Ansatz hatte, hauptsächlich weil ich versuchte, die Ausgabe in eine Datei zu schreiben, und sie nicht richtig codiert war. Da JS anscheinend UCS-2-Codierung ( Quelle , Quelle ) verwendet, müssen wir diese Lösung noch einen Schritt weiter ausdehnen. Hier ist meine erweiterte Lösung, die für mich funktioniert.

Ich hatte keine Schwierigkeiten mit generischem Text, aber wenn es sich um Arabisch oder Koreanisch handelte, enthielt die Ausgabedatei nicht alle Zeichen, sondern zeigte stattdessen Fehlerzeichen

Dateiausgabe: ","10k unit":"",Follow:"Õ©íüY‹","Follow %{screen_name}":"%{screen_name}U“’Õ©íü",Tweet:"ĤüÈ","Tweet %{hashtag}":"%{hashtag} ’ĤüÈY‹","Tweet to %{name}":"%{name}U“xĤüÈY‹"},ko:{"%{followers_count} followers":"%{followers_count}…X \Ì","100K+":"100Ì tÁ","10k unit":"Ì è",Follow:"\°","Follow %{screen_name}":"%{screen_name} Ø \°X0",K:"œ",M:"1Ì",Tweet:"¸","Tweet %{hashtag}":"%{hashtag}

Original: ","10k unit":"万",Follow:"フォローする","Follow %{screen_name}":"%{screen_name}さんをフォロー",Tweet:"ツイート","Tweet %{hashtag}":"%{hashtag} をツイートする","Tweet to %{name}":"%{name}さんへツイートする"},ko:{"%{followers_count} followers":"%{followers_count}명의 팔로워","100K+":"100만 이상","10k unit":"만 단위",Follow:"팔로우","Follow %{screen_name}":"%{screen_name} 님 팔로우하기",K:"천",M:"백만",Tweet:"트윗","Tweet %{hashtag}":"%{hashtag}

Ich habe die Informationen aus Dennis 'Lösung genommen und diesen Beitrag gefunden.

Hier ist mein Code:

function encode_utf8(s) {
  return unescape(encodeURIComponent(s));
}

function decode_utf8(s) {
  return decodeURIComponent(escape(s));
}

 function ab2str(buf) {
   var s = String.fromCharCode.apply(null, new Uint8Array(buf));
   return decode_utf8(decode_utf8(s))
 }

function str2ab(str) {
   var s = encode_utf8(str)
   var buf = new ArrayBuffer(s.length); 
   var bufView = new Uint8Array(buf);
   for (var i=0, strLen=s.length; i<strLen; i++) {
     bufView[i] = s.charCodeAt(i);
   }
   return bufView;
 }

Dadurch kann ich den Inhalt ohne Codierungsprobleme in einer Datei speichern.

So funktioniert es: Grundsätzlich werden die einzelnen 8-Byte-Blöcke, aus denen ein UTF-8-Zeichen besteht, als einzelne Zeichen gespeichert (daher kann ein auf diese Weise erstelltes UTF-8-Zeichen aus 1 bis 4 dieser Zeichen bestehen). UTF-8 codiert Zeichen in einem Format mit einer Länge von 1 bis 4 Byte. Was wir hier tun, ist, den Stich in einer URI-Komponente zu codieren und dann diese Komponente zu nehmen und sie in das entsprechende 8-Byte-Zeichen zu übersetzen. Auf diese Weise verlieren wir nicht die Informationen von UTF8-Zeichen, die länger als 1 Byte sind.


6

Wenn Sie ein Beispiel für ein großes Array verwendet haben arr.length=1000000 , können Sie diesen Code verwenden, um Probleme mit dem Stapelrückruf zu vermeiden

function ab2str(buf) {
var bufView = new Uint16Array(buf);
var unis =""
for (var i = 0; i < bufView.length; i++) {
    unis=unis+String.fromCharCode(bufView[i]);
}
return unis
}

Umkehrfunktion Mangini Antwort von oben

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;
}

4

Nun, hier ist eine etwas verschlungene Art, dasselbe zu tun:

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());

Bearbeiten: BlobBuilder ist seit langem zugunsten des Blob-Konstruktors veraltet, der nicht existierte, als ich diesen Beitrag zum ersten Mal schrieb. Hier ist eine aktualisierte Version. (Und ja, das war schon immer eine sehr dumme Art, die Konvertierung durchzuführen, aber es war nur zum Spaß!)

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]));

3

Nach dem Spielen mit Mangini-Lösung zur Umwandlung von ArrayBufferbis String- ab2str(das ist die eleganteste und nützlich ist die ich gefunden habe - danke), hatte ich einige Probleme , wenn große Arrays Handhabung. Insbesondere ruft das Aufrufen String.fromCharCode.apply(null, new Uint16Array(buf));einen Fehler auf:

arguments array passed to Function.prototype.apply is too large.

Um es zu lösen (Bypass), habe ich beschlossen, die Eingabe ArrayBufferin Blöcken zu behandeln. Die modifizierte Lösung lautet also:

function ab2str(buf) {
   var str = "";
   var ab = new Uint16Array(buf);
   var abLen = ab.length;
   var CHUNK_SIZE = Math.pow(2, 16);
   var offset, len, subab;
   for (offset = 0; offset < abLen; offset += CHUNK_SIZE) {
      len = Math.min(CHUNK_SIZE, abLen-offset);
      subab = ab.subarray(offset, offset+len);
      str += String.fromCharCode.apply(null, subab);
   }
   return str;
}

Die Blockgröße ist festgelegt, 2^16da dies die Größe war, die ich in meiner Entwicklungslandschaft gefunden habe. Das Einstellen eines höheren Werts führte dazu, dass derselbe Fehler erneut auftrat. Sie kann geändert werden, indem Sie die CHUNK_SIZEVariable auf einen anderen Wert setzen. Es ist wichtig, eine gerade Zahl zu haben.

Hinweis zur Leistung - Ich habe für diese Lösung keine Leistungstests durchgeführt. Da es jedoch auf der vorherigen Lösung basiert und große Arrays verarbeiten kann, sehe ich keinen Grund, es nicht zu verwenden.


Sie können typedarray.subarray verwenden , um einen Block an der angegebenen Position und Größe zu erhalten. Dies ist, was ich tue, um Header aus Binärformaten in js zu lesen
Nikos M.


2
  stringToArrayBuffer(byteString) {
    var byteArray = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      byteArray[i] = byteString.codePointAt(i);
    }
    return byteArray;
  }
  arrayBufferToString(buffer) {
    var byteArray = new Uint8Array(buffer);
    var byteString = '';
    for (var i = 0; i < byteArray.byteLength; i++) {
      byteString += String.fromCodePoint(byteArray[i]);
    }
    return byteString;
  }

Dieser Code ist fehlerhaft, wenn die Zeichenfolge Unicode-Zeichen enthält. Beispiel:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp

2

Für node.js und auch für Browser, die https://github.com/feross/buffer verwenden

function ab2str(buf: Uint8Array) {
  return Buffer.from(buf).toString('base64');
}
function str2ab(str: string) {
  return new Uint8Array(Buffer.from(str, 'base64'))
}

Hinweis: Die Lösungen hier haben bei mir nicht funktioniert. Ich muss node.js und Browser unterstützen und UInt8Array einfach in eine Zeichenfolge serialisieren. Ich könnte es als Nummer [] serialisieren, aber das nimmt unnötigen Platz ein. Mit dieser Lösung muss ich mich nicht um Codierungen kümmern, da es base64 ist. Nur für den Fall, dass andere Leute mit dem gleichen Problem zu kämpfen haben ... Meine zwei Cent


2

Angenommen, Sie haben einen arrayBuffer binaryStr:

let text = String.fromCharCode.apply(null, new Uint8Array(binaryStr));

und dann ordnen Sie den Text dem Staat zu.


1

Die "native" Binärzeichenfolge, die atob () zurückgibt, ist ein Array mit 1 Byte pro Zeichen.

Wir sollten also keine 2 Bytes in einem Zeichen speichern.

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;
}

1

Ja:

const encstr = (`TextEncoder` in window) ? new TextEncoder().encode(str) : Uint8Array.from(str, c => c.codePointAt(0));

0

Ich würde empfehlen, KEINE veralteten APIs wie BlobBuilder zu verwenden

BlobBuilder ist seit langem vom Blob-Objekt veraltet. Vergleichen Sie den Code in Dennis 'Antwort - wo BlobBuilder verwendet wird - mit dem folgenden Code:

function arrayBufferGen(str, cb) {

  var b = new Blob([str]);
  var f = new FileReader();

  f.onload = function(e) {
    cb(e.target.result);
  }

  f.readAsArrayBuffer(b);

}

Beachten Sie, wie viel sauberer und weniger aufgebläht dies im Vergleich zur veralteten Methode ist ... Ja, das ist hier definitiv etwas zu beachten.


Ich meine, ja, aber dieser Blob-Konstruktor war 2012 nicht wirklich verwendbar;)
gengkev


0

Ich habe das benutzt und arbeite für mich.

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;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.