Pregunta Conversión entre cadenas y ArrayBuffers


¿Existe una técnica comúnmente aceptada para convertir eficientemente cadenas de JavaScript a ArrayBuffers ¿y viceversa? Específicamente, me gustaría poder escribir el contenido de un ArrayBuffer a localStorage y leerlo de nuevo.


180
2017-08-06 06:01


origen


Respuestas:


Actualización 2016 - Cinco años después, ahora hay nuevos métodos en las especificaciones (ver soporte a continuación) para convertir cadenas y matrices tipadas usando la codificación adecuada.

TextEncoder

los TextEncoder representa:

los TextEncoder la interfaz representa un codificador para un método específico,   esa es una codificación de caracteres específica, como utf-8, iso-8859-2, koi8,    cp1261, gbk, ... Un codificador toma una secuencia de puntos de código como entrada y   emite una secuencia de bytes.

Nota de cambio ya que lo anterior fue escrito: (ibídem.)

Nota: Firefox, Chrome y Opera solían ser compatibles con la codificación   tipos distintos de utf-8 (como utf-16, iso-8859-2, koi8, cp1261 y   gbk). A partir de Firefox 48 [...], Chrome 54 y Opera 41, sin   hay otros tipos de codificación disponibles además de utf-8, para hacer coincidir   la especificación *

*) Especificaciones actualizadas (W3) y aquí (whatwg)

Después de crear una instancia de TextEncoder tomará una cadena y la codificará usando un parámetro de codificación dado:

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

Entonces, por supuesto, usa el .buffer parámetro en el resultado Uint8Array para convertir la subcapa ArrayBuffer a una vista diferente si es necesario.

Solo asegúrese de que los caracteres de la cadena se adhieran al esquema de codificación, por ejemplo, si utiliza caracteres fuera del rango UTF-8 en el ejemplo, se codificarán en dos bytes en lugar de uno.

Para uso general, usaría la codificación UTF-16 para cosas como localStorage.

TextDecoder

Del mismo modo, el proceso opuesto usa el TextDecoder:

los TextDecoder la interfaz representa un decodificador para un método específico,   esa es una codificación de caracteres específica, como utf-8, iso-8859-2, koi8,    cp1261, gbk, ... Un decodificador toma una secuencia de bytes como entrada y emite   una secuencia de puntos de código.

Todos los tipos de descodificación disponibles se pueden encontrar aquí.

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 biblioteca MDN StringView

Una alternativa a estos es usar el StringView biblioteca (con licencia como lgpl-3.0) cuyo objetivo es:

  • para crear una interfaz tipo C para cadenas (es decir, una matriz de códigos de caracteres, una ArrayBufferView en JavaScript) basada en   Interfaz JavaScript ArrayBuffer
  • para crear una biblioteca altamente extensible que cualquiera pueda ampliar agregando métodos al objeto StringView.prototype
  • para crear una colección de métodos para tales objetos tipo cadena (desde ahora: stringViews) que funcionan estrictamente en matrices de números   en lugar de crear nuevas cadenas de JavaScript inmutables
  • para trabajar con codificaciones Unicode distintas de las DOMStrings UTF-16 predeterminadas de JavaScript

dando mucha más flexibilidad. Sin embargo, sería necesario que vinculemos o incorporemos esta biblioteca mientras TextEncoder/TextDecoder está siendo incorporado en los navegadores modernos.

Apoyo

A partir de julio / 2018:

TextEncoder (Experimental, en pista estándar)

 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

49
2018-06-18 22:50



Aunque las soluciones de Dennis y gengkev para usar Blob / FileReader funcionan, no sugeriría tomar ese enfoque. Es un enfoque asíncrono a un problema simple, y es mucho más lento que una solución directa. Hice una publicación en html5rocks con una solución más simple y (mucho más rápida): http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

Y la solución es:

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

EDITAR:

los La codificación API ayuda a resolver la conversión de cadenas problema. Mira la respuesta de Jeff Posnik en Html5Rocks.com al artículo original anterior.

Extracto:

La API de codificación facilita la traducción entre bytes sin formato y cadenas de JavaScript nativas, independientemente de la cantidad de codificaciones estándar con las que necesite trabajar.

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

161
2018-06-15 22:17



Puedes usar TextEncoder y TextDecoder desde el Norma de codificación, que es polyfilled por el biblioteca de stringencoding, para convertir cadenas hacia y desde ArrayBuffers:

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

58
2017-07-10 10:30



Blob es mucho más lento que String.fromCharCode(null,array);

pero eso falla si el buffer de la matriz se vuelve demasiado grande. La mejor solución que he encontrado es usar String.fromCharCode(null,array); y divídalo en operaciones que no volarán la pila, pero son más rápidas que un solo char a la vez.

La mejor solución para el búfer de matriz grande es:

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;

}

Encontré que esto es aproximadamente 20 veces más rápido que usar blob. También funciona para cadenas grandes de más de 100 mb.


30
2017-12-16 06:12



Basado en la respuesta de gengkev, creé funciones para ambos sentidos, porque BlobBuilder puede manejar String y 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());
}

y

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

Una simple prueba:

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

20
2018-03-12 18:53



(Actualizar Consulte la segunda mitad de esta respuesta, donde (afortunadamente) proporcioné una solución más completa).

También me encontré con este problema, los siguientes trabajos para mí en FF 6 (para una dirección):

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

Desafortunadamente, por supuesto, terminas con representaciones de texto ASCII de los valores en la matriz, en lugar de caracteres. Todavía (debería ser) mucho más eficiente que un bucle, sin embargo. p.ej. Para el ejemplo anterior, el resultado es 0004000000, en lugar de varios caracteres nulos y un chr (4).

Editar:

Después de mirar MDC  aquí, puedes crear un ArrayBuffer desde un Array como sigue:

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;

Para responder a su pregunta original, esto le permite convertir ArrayBuffer <-> String como sigue:

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

Por conveniencia, aquí hay una function para convertir un Unicode sin formato String a una ArrayBuffer (solo funcionará con caracteres ASCII / de 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 ]);

Lo anterior le permite ir desde ArrayBuffer -> String & de regreso ArrayBuffer de nuevo, donde la cadena se puede almacenar, por ej. .localStorage :)

Espero que esto ayude,

Dan


12
2017-08-24 23:29



Todo lo siguiente se trata de obtener cadenas binarias de los búferes de matriz

Yo recomendaría no usar

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

porque

  1. choques en grandes búferes (alguien escribió sobre el tamaño "mágico" de 246300 pero obtuve Maximum call stack size exceeded error en el búfer 120000 bytes (Chrome 29)
  2. Tiene rendimiento muy pobre (vea abajo)

Si necesita exactamente una solución sincrónica, use algo como

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

es tan lento como el anterior pero funciona correctamente. Parece que al momento de escribir esto no existe una solución síncrona bastante rápida para ese problema (todas las bibliotecas mencionadas en este tema usan el mismo enfoque para sus características síncronas).

Pero lo que realmente recomiendo es usar Blob + FileReader enfoque

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

la única desventaja (no para todos) es que es asincrónico. Y se trata de 8-10 veces más rápido entonces soluciones anteriores! (Algunos detalles: la solución sincrónica en mi entorno tomó 950-1050 ms para el buffer de 2.4Mb pero la solución con FileReader tuvo tiempos de aproximadamente 100-120 ms para la misma cantidad de datos. Y he probado ambos soluciones sincrónicas en un buffer de 100Kb y han tomado casi el mismo tiempo, por lo que el bucle no es mucho más lento al usar 'apply').

Por cierto aquí: Cómo convertir ArrayBuffer hacia y desde String el autor compara dos enfoques como yo y obtiene resultados completamente opuestos (su código de prueba está aquí) ¿Por qué tan diferentes resultados? Probablemente debido a su cadena de prueba que tiene 1Kb de longitud (lo llamó "veryLongStr"). Mi buffer era una imagen JPEG realmente grande de tamaño 2.4Mb.


12
2017-09-05 19:13