Conversione tra stringhe e ArrayBuffer


276

Esiste una tecnica comunemente accettata per convertire in modo efficiente le stringhe JavaScript in ArrayBuffer e viceversa? In particolare, mi piacerebbe essere in grado di scrivere il contenuto di un ArrayBuffer localStoragee di leggerlo nuovamente.


1
Non ho alcuna esperienza in questo, ma a giudicare dalla documentazione API ( khronos.org/registry/typedarray/specs/latest ) se costruisci un Int8Array ArrayBufferViewpotrebbe essere possibile utilizzare semplicemente la notazione delle parentesi per copiare i caratteri string[i] = buffer[i]e viceversa.
FK82

2
@ FK82, sembra un approccio ragionevole (utilizzando Uint16Arrays per i caratteri a 16 bit di JS), ma le stringhe JavaScript sono immutabili quindi non è possibile assegnare direttamente a una posizione di carattere. Vorrei ancora bisogno di copiare String.fromCharCode(x)di ogni valore in Uint16Arrayuna normale Arraye quindi chiamare .join()il Array.
kpozin

@kpozin: Vero, non ci ho davvero pensato.
FK82

5
@kpozin Si scopre che la maggior parte dei moderni motori JS ha ottimizzato la concatenazione di stringhe al punto che è più economico da usare string += String.fromCharCode(buffer[i]);. Sembra strano che non ci siano metodi integrati per la conversione tra stringhe e array digitati. Dovevano sapere che sarebbe successo qualcosa del genere.
scarica il

arrayBuffer.toString () funziona bene per me.
conn. Cittadino

Risposte:


142

Aggiornamento 2016 : cinque anni dopo ora sono disponibili nuovi metodi nelle specifiche (vedere il supporto di seguito) per convertire tra stringhe e array digitati utilizzando la codifica corretta.

TextEncoder

Il TextEncoderrappresenta :

L' TextEncoderinterfaccia rappresenta un codificatore per un metodo specifico, ovvero una codifica di caratteri specifica, come utf-8,iso-8859-2, koi8, cp1261, gbk, ... Un codificatore accetta un flusso di punti di codice come input ed emette un flusso di byte.

Nota di modifica poiché quanto sopra è stato scritto: (ibid.)

Nota: Firefox, Chrome e Opera supportavano tipi di codifica diversi da utf-8 (come utf-16, iso-8859-2, koi8, cp1261 e gbk). A partire da Firefox 48 [...], Chrome 54 [...] e Opera 41, non sono disponibili altri tipi di codifica oltre a utf-8, al fine di soddisfare le specifiche. *

*) Specifiche aggiornate (W3) e qui (whatwg).

Dopo aver creato un'istanza di TextEncoder, prenderà una stringa e la codificherà utilizzando un dato parametro di codifica:

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

Ovviamente si utilizza quindi il .bufferparametro sul risultato Uint8Arrayper convertire il sottofondo ArrayBufferin una vista diversa, se necessario.

Assicurati solo che i caratteri nella stringa aderiscano allo schema di codifica, ad esempio, se usi caratteri al di fuori dell'intervallo UTF-8 nell'esempio, saranno codificati a due byte invece di uno.

Per uso generale useresti la codifica UTF-16 per cose come localStorage.

TextDecoder

Allo stesso modo, il processo opposto utilizzaTextDecoder :

L' TextDecoderinterfaccia rappresenta un decodificatore per un metodo specifico, che è una codifica specifica, come utf-8, iso-8859-2, koi8, cp1261, gbk, ... Un decodificatore prende un flusso di byte in ingresso ed emette un flusso di punti di codice.

Tutti i tipi di decodifica disponibili possono essere trovati qui .

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

La libreria MDN StringView

Un'alternativa a questi è usare la StringViewlibreria (con licenza lgpl-3.0) il cui obiettivo è:

  • per creare un'interfaccia simile al C per le stringhe (cioè, un array di codici di caratteri - un ArrayBufferView in JavaScript) basata sull'interfaccia JavaScript ArrayBuffer
  • per creare una libreria altamente estensibile che chiunque può estendere aggiungendo metodi all'oggetto StringView.prototype
  • creare una raccolta di metodi per tali oggetti simili a stringhe (da ora: stringViews) che funzionano rigorosamente su array di numeri piuttosto che sulla creazione di nuove stringhe JavaScript immutabili
  • per lavorare con codifiche Unicode diverse dalle stringhe DOMStrings UTF-16 predefinite di JavaScript

dando molta più flessibilità. Tuttavia, ci richiederebbe di collegare o incorporare questa libreria mentre TextEncoder/ TextDecoderè integrato nei browser moderni.

Supporto

A partire da luglio / 2018:

TextEncoder (Sperimentale, su traccia standard)

 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
Nessun supporto per TextDecoder da IE & Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete


Nessun supporto per Safari Mobile (ios) a 2018-04-18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
bronze man

One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};così puoi semplicementevar array = encoder.encode('hello');
Yeti

1
Il problema TextEncoderè che se hai dati binari in una stringa (come, immagine), non vuoi usare TextEncoder(apparentemente). I caratteri con punti di codice maggiori di 127 producono due byte. Perché ho dati binari in una stringa? cy.fixture(NAME, 'binary')( cypress) produce una stringa.
x-yuri

176

Sebbene le soluzioni di Dennis e gengkev per l'utilizzo di Blob / FileReader funzionino, non suggerirei di adottare questo approccio. È un approccio asincrono a un problema semplice ed è molto più lento di una soluzione diretta. Ho scritto un post in html5rocks con una soluzione più semplice e (molto più veloce): http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

E la soluzione è:

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

MODIFICARE:

L' API di codifica aiuta a risolvere il problema di conversione delle stringhe . Controlla la risposta di Jeff Posnik su Html5Rocks.com all'articolo originale sopra.

Estratto:

L'API di codifica semplifica la traduzione tra byte grezzi e stringhe JavaScript native, indipendentemente da quale delle molte codifiche standard è necessario utilizzare.

<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
Purtroppo il mio commento su html5rocks non è ancora stato approvato. Quindi una breve risposta qui. Continuo a pensare, questo non è il modo giusto, perché ti mancano molti caratteri, soprattutto perché la maggior parte delle pagine oggi è in codifica UTF-8. Da un lato, per più caratteri speciali (diciamo asiatici), la funzione charCodeAt restituisce un valore di 4 byte, quindi verranno tagliati. Dall'altro lato, i caratteri inglesi semplici aumenteranno il ArrayBuffer due volte (stai usando 2 byte per ogni carattere da 1 byte). Immagina di inviare un testo in inglese su un WebSocket, richiederà il doppio del tempo (non va bene in ambiente in tempo reale).
Dennis

9
Tre esempi: (1) This is a cool text!20 byte in UTF8 - 40 byte in Unicode. (2) ÄÖÜ6 byte in UTF8 - 6 byte in Unicode. (3) ☐☑☒9 byte in UTF8 - 6 byte in Unicode. Se si desidera memorizzare la stringa come file UTF8 (tramite Blob e API File Writer), non è possibile utilizzare questi 2 metodi, poiché ArrayBuffer sarà in Unicode e non in UTF8.
Dennis

3
Ricevo un errore: Uncaught RangeError: dimensione massima dello stack di chiamate superata. Quale potrebbe essere il problema?
Jacob,

6
@Dennis - Le stringhe JS usano UCS2, non UTF8 (o anche UTF16) - il che significa che charCodeAt () restituisce sempre valori 0 -> 65535. Qualsiasi punto di codice UTF-8 che richiede 4 byte finali sarà rappresentato con coppie surrogate (vedi en.wikipedia .org / wiki /… ) - cioè due valori UCS2 a 16 bit separati.
broofa

6
@jacob - Credo che l'errore sia dovuto al fatto che c'è un limite alla lunghezza dell'array che può essere passato al metodo apply (). Ad esempio, String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthfunziona per me in Chrome, ma se usi invece 246301, ottengo l'eccezione
RangeError

74

È possibile utilizzare TextEncodere TextDecoderdallo standard Encoding , che è polyfilled dalla libreria stringencoding , per convertire la stringa da e verso ArrayBuffers:

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

2
A proposito, questo è disponibile in Firefox per impostazione predefinita: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard

2
Complimenti per le nuove API che sono molto meglio di strane soluzioni alternative!
Tomáš Zato - Ripristina Monica il

1
Questo non funzionerà con tutti i tipi di personaggi là fuori.
David

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

brontolare ... se ho un arraybuffer esistente in cui voglio scrivere una stringa immagino di dover prendere uint8array e copiarlo una seconda volta ??
Shaunc il

40

Blob è molto più lento di String.fromCharCode(null,array);

ma fallisce se il buffer dell'array diventa troppo grande. La soluzione migliore che ho trovato è usarla String.fromCharCode(null,array);e suddividerla in operazioni che non faranno saltare lo stack, ma sono più veloci di un singolo carattere alla volta.

La soluzione migliore per un buffer di array di grandi dimensioni è:

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;

}

Ho scoperto che questo è circa 20 volte più veloce rispetto all'utilizzo di BLOB. Funziona anche per grandi stringhe di oltre 100 MB.


3
Dovremmo seguire questa soluzione. Poiché questo risolve un caso d'uso in più rispetto a quello accettato
sam il

1
Ottengo: "Uncaught INVALID: decodifica json: questo non è json!"
dicroce

24

Sulla base della risposta di gengkev, ho creato funzioni per entrambi i modi, perché BlobBuilder può gestire String e ArrayBuffer:

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

e

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

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

In arrayBuffer2String (), intendevi chiamare callback (...) invece di console.log ()? In caso contrario, l'argomento callback non viene utilizzato.
Dan Phillimore

Questa sembra la strada da percorrere - grazie genkev e Dennis. Sembra un po 'sciocco che non ci sia un modo sincrono per ottenere questo risultato, ma cosa puoi fare ...
kpozin

JavaScript è a thread singolo. Pertanto il FileReader è asincrono per due motivi: (1) non bloccherà l'esecuzione di altri JavaScript durante il caricamento di un file (enorme) (immagina un'applicazione più complessa) e (2) non bloccherà l'interfaccia utente / browser (problema comune con codice JS a lunga esecuzione). Molte API sono asincrone. Anche in XMLHttpRequest 2 il sincrono viene rimosso.
Dennis

Speravo davvero che funzionasse per me, ma la conversione da stringa a ArrayBuffer non funziona in modo affidabile. Sto creando un ArrayBuffer con 256 valori e posso trasformarlo in una stringa con lunghezza 256. Ma poi se provo a convertirlo di nuovo in un ArrayBuffer - a seconda del contenuto del mio ArrayBuffer iniziale - ottengo 376 elementi. Se vuoi provare a riprodurre il mio problema, sto trattando il mio ArrayBuffer come una griglia 16x16 in un Uint8Array, con valori calcolati come a[y * w + x] = (x + y) / 2 * 16; ho provato getBlob("x"), con molti tipi mimety diversi - senza fortuna.
Matt Cruikshank

18
BlobBuilder è deprecato nei browser più recenti. Passare new BlobBuilder(); bb.append(buf);a new Blob([buf]), eseguire il cast di ArrayBuffer nella seconda funzione a un UintArray tramite new UintArray(buf)(o qualsiasi altra cosa sia appropriata per il tipo di dati sottostante), quindi eliminare le getBlob()chiamate. Infine, per la pulizia, rinomina bb in blob perché non è più un BlobBuilder.
sowbug

18

Quanto segue riguarda l'acquisizione di stringhe binarie dai buffer di array

Consiglierei di non usare

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

perché

  1. si blocca su buffer di grandi dimensioni (qualcuno ha scritto della dimensione "magica" di 246300 ma ho ricevuto un Maximum call stack size exceedederrore sul buffer di 120000 byte (Chrome 29))
  2. ha prestazioni davvero scadenti (vedi sotto)

Se hai esattamente bisogno di una soluzione sincrona usa qualcosa come

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

è lento come il precedente ma funziona correttamente. Sembra che al momento in cui scrivo non ci sia una soluzione sincrona abbastanza veloce per quel problema (tutte le librerie menzionate in questo argomento usano lo stesso approccio per le loro caratteristiche sincrone).

Ma quello che consiglio davvero è usare l' approccio 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' }));
}

l'unico svantaggio (non per tutti) è che è asincrono . Ed è circa 8-10 volte più veloce delle soluzioni precedenti! (Alcuni dettagli: la soluzione sincrona sul mio ambiente ha richiesto 950-1050 ms per un buffer da 2,4 Mb ma la soluzione con FileReader ha avuto tempi di circa 100-120 ms per la stessa quantità di dati. E ho testato entrambe le soluzioni sincrone su un buffer da 100 KB e hanno preso quasi nello stesso momento, quindi il ciclo non è molto più lento dell'uso di "applica".)

BTW qui: come convertire ArrayBuffer in e da String author confronta due approcci come me e ottiene risultati completamente opposti (il suo codice di test è qui ) Perché risultati così diversi? Probabilmente a causa della sua stringa di prova lunga 1Kb (l'ha chiamata "veryLongStr"). Il mio buffer era un'immagine JPEG molto grande di 2,4 Mb.


13

( Aggiornamento Si prega di vedere la seconda metà di questa risposta, dove ho (si spera) fornito una soluzione più completa.)

Ho anche riscontrato questo problema, il seguente funziona per me in FF 6 (per una direzione):

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

Sfortunatamente, ovviamente, ti ritroverai con rappresentazioni di testo ASCII dei valori nell'array, piuttosto che caratteri. Tuttavia (dovrebbe essere) molto più efficiente di un ciclo. per esempio. Per l'esempio sopra, il risultato è 0004000000, piuttosto che diversi caratteri nulli e un chr (4).

Modificare:

Dopo aver esaminato MDC qui , puoi creare un ArrayBufferda un Arraycome segue:

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;

Per rispondere alla tua domanda originale, questo ti consente di convertire ArrayBuffer<-> Stringcome segue:

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

Per comodità, ecco un functionper convertire un Unicode grezzo Stringin un ArrayBuffer(funzionerà solo con caratteri ASCII / un byte)

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

Quanto sopra ti permette di andare da ArrayBuffer-> Stringe di ArrayBuffernuovo a, dove la stringa può essere memorizzata ad es. .localStorage:)

Spero che sia di aiuto,

Dan


1
Non credo che questo sia un metodo efficiente (in termini di tempo o spazio), e questo è un modo molto insolito per memorizzare dati binari.
kpozin

@kpozin: Per quanto ne so, non c'è altro modo per memorizzare dati binari in localStorage
Dan Phillimore,

1
Che dire dell'utilizzo della codifica base64?
Nick Sotiros

13

A differenza delle soluzioni qui, avevo bisogno di convertire da / a dati UTF-8. A questo scopo, ho codificato le seguenti due funzioni, usando il trucco (un) escape / (en) decodeURIComponent. Sono piuttosto dispendiosi in termini di memoria, allocando 9 volte la lunghezza della stringa utf8 codificata, anche se dovrebbero essere recuperati da gc. Basta non usarli per 100 MB di testo.

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

Verifica che funzioni:

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

10

Nel caso in cui tu abbia dati binari in una stringa (ottenuta da nodejs+ readFile(..., 'binary'), o cypress+ cy.fixture(..., 'binary'), ecc.), Non puoi usare TextEncoder. Supporta solo utf8. I byte con valori >= 128vengono trasformati ciascuno in 2 byte.

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

Ho scoperto di avere problemi con questo approccio, fondamentalmente perché stavo cercando di scrivere l'output su un file e non era codificato correttamente. Poiché JS sembra utilizzare la codifica UCS-2 ( sorgente , sorgente ), dobbiamo estendere ulteriormente questa soluzione, ecco la mia soluzione migliorata che funziona per me.

Non ho avuto difficoltà con il testo generico, ma quando era arabo o coreano, il file di output non aveva tutti i caratteri ma mostrava invece caratteri di errore

Uscita file: ","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}

Originale: ","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}

Ho preso le informazioni dalla soluzione di Dennis e ho trovato questo post .

Ecco il mio codice:

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

Questo mi permette di salvare il contenuto in un file senza problemi di codifica.

Come funziona: Prende fondamentalmente i singoli blocchi di 8 byte che compongono un carattere UTF-8 e li salva come caratteri singoli (quindi un carattere UTF-8 costruito in questo modo, potrebbe essere composto da 1-4 di questi caratteri). UTF-8 codifica i caratteri in un formato che varia da 1 a 4 byte di lunghezza. Quello che facciamo qui è codificare la puntura in un componente URI e quindi prendere questo componente e tradurlo nel corrispondente carattere di 8 byte. In questo modo non perdiamo le informazioni fornite dai caratteri UTF8 di lunghezza superiore a 1 byte.


6

se hai usato un esempio di array enorme, puoi usare arr.length=1000000 questo codice per evitare problemi di callback dello stack

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
}

funzione inversa mangini risponde dall'alto

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

Bene, ecco un modo un po 'complicato di fare la stessa cosa:

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

Modifica: BlobBuilder è stato a lungo deprecato a favore del costruttore Blob, che non esisteva quando ho scritto per la prima volta questo post. Ecco una versione aggiornata. (E sì, questo è sempre stato un modo molto stupido per eseguire la conversione, ma era solo per divertimento!)

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

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

questo codice è difettoso se la stringa contiene caratteri Unicode. esempio:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp

3

Dopo aver giocato con la soluzione di mangini per la conversione da ArrayBuffera String- ab2str(che è la più elegante e utile che ho trovato - grazie!), Ho avuto alcuni problemi durante la gestione di grandi array. Più specificatamente, la chiamata String.fromCharCode.apply(null, new Uint16Array(buf));genera un errore:

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

Per risolverlo (bypass) ho deciso di gestire l'input ArrayBufferin blocchi. Quindi la soluzione modificata è:

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

La dimensione del blocco è impostata su 2^16perché questa era la dimensione che ho trovato per funzionare nel mio panorama di sviluppo. L'impostazione di un valore più alto ha causato il ripetersi dello stesso errore. Può essere modificato impostando la CHUNK_SIZEvariabile su un valore diverso. È importante avere un numero pari.

Nota sulle prestazioni: non ho effettuato alcun test delle prestazioni per questa soluzione. Tuttavia, poiché si basa sulla soluzione precedente e può gestire array di grandi dimensioni, non vedo motivo per non utilizzarlo.


puoi usare typedarray.subarray per ottenere un pezzo nella posizione e dimensione specificate, questo è quello che faccio per leggere le intestazioni dai formati binari in js
Nikos M.


2

La stringa binaria "nativa" restituita da atob () è un array da 1 byte per carattere.

Quindi non dovremmo memorizzare 2 byte in un carattere.

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

2

Per node.js e anche per i browser che utilizzano https://github.com/feross/buffer

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

Nota: le soluzioni qui non hanno funzionato per me. Devo supportare node.js e browser e serializzare UInt8Array su una stringa. Potrei serializzarlo come un numero [] ma questo occupa uno spazio non necessario. Con quella soluzione non devo preoccuparmi delle codifiche poiché è base64. Nel caso in cui altre persone lottino con lo stesso problema ... I miei due centesimi


2

Supponiamo che tu abbia un arrayBuffer binaryStr:

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

e poi assegni il testo allo stato.


1

Sì:

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

0

Consiglierei di NON utilizzare API deprecate come BlobBuilder

BlobBuilder è stato a lungo deprecato dall'oggetto Blob. Confronta il codice nella risposta di Dennis, dove viene utilizzato BlobBuilder, con il codice seguente:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

Nota quanto è più pulito e meno gonfio rispetto al metodo deprecato ... Sì, questo è sicuramente qualcosa da considerare qui.


Voglio dire, sì, ma quel costruttore Blob non era realmente utilizzabile nel 2012;)
gengkev


0

L'ho usato e funziona per me.

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

0

Quella che segue è un'implementazione di Typescript funzionante:

bufferToString(buffer: ArrayBuffer): string {
    return String.fromCharCode.apply(null, Array.from(new Uint16Array(buffer)));
}

stringToBuffer(value: string): ArrayBuffer {
    let buffer = new ArrayBuffer(value.length * 2); // 2 bytes per char
    let view = new Uint16Array(buffer);
    for (let i = 0, length = value.length; i < length; i++) {
        view[i] = value.charCodeAt(i);
    }
    return buffer;
}

L'ho usato per numerose operazioni mentre lavoravo con crypto.subtle .


0

Da emscripten:

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
}

Usa come:

stringToUTF8Array('abs', new Uint8Array(3), 0, 4);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.