Conversión entre cadenas y ArrayBuffers

Resuelto kpozin asked hace 13 años • 29 respuestas

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

kpozin avatar Aug 06 '11 13:08 kpozin
Aceptado

Actualización 2016 : cinco años después, ahora hay nuevos métodos en las especificaciones (consulte el soporte a continuación) para convertir entre cadenas y matrices escritas utilizando la codificación adecuada.

Codificador de texto

El TextEncoderrepresenta :

La TextEncoderinterfaz representa un codificador para un método específico, es decir, una codificación de caracteres específica, como utf-8,iso-8859-2, koi8, cp1261, gbk, ...Un codificador toma un flujo de puntos de código como entrada y emite un flujo de bytes.

Nota de cambio desde que se escribió lo anterior: (ibid.)

Nota: Firefox, Chrome y Opera solían admitir tipos de codificación distintos de utf-8 (como utf-16, iso-8859-2, koi8, cp1261 y gbk). A partir de Firefox 48 [...], Chrome 54 [...] y Opera 41, no hay otros tipos de codificación disponibles aparte de utf-8, para cumplir con las especificaciones.*

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

Después de crear una instancia, TextEncodertomará una cadena y la codificará usando un parámetro de codificación determinado:

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

Luego, por supuesto, utiliza el .bufferparámetro en el resultado Uint8Arraypara convertir la capa subyacente ArrayBuffera 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 usa 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.

Decodificador de texto

Asimismo, el proceso opuesto utilizaTextDecoder :

La TextDecoderinterfaz representa un decodificador para un método específico, es decir, una codificación de caracteres específica, como utf-8, iso-8859-2, koi8, cp1261, gbk... Un decodificador toma un flujo de bytes como entrada y emite un flujo de puntos de código.

Todos los tipos de decodificació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));
Expandir fragmento

La biblioteca MDN StringView

Una alternativa a estas es utilizar la StringViewbiblioteca (con licencia lgpl-3.0) cuyo objetivo es:

  • para crear una interfaz tipo C para cadenas (es decir, una matriz de códigos de caracteres, un ArrayBufferView en JavaScript) basada en la 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 objetos similares a cadenas (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 a las DOMStrings UTF-16 predeterminadas de JavaScript

dando mucha más flexibilidad. Sin embargo, sería necesario que vinculáramos o incrustáramos esta biblioteca mientras TextEncoder/ TextDecoderse integra en los navegadores modernos.

Apoyo

A 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
 avatar Jun 18 '2016 22:06

Aunque las soluciones de Dennis y gengkev para usar Blob/FileReader funcionan, no sugeriría adoptar ese enfoque. Es un enfoque asíncrono para 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:

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

Extracto:

La API de codificación simplifica la traducción entre bytes sin formato y cadenas de JavaScript nativas, independientemente de con cuál de las muchas codificaciones estándar 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>
mangini avatar Jun 15 '2012 22:06 mangini

Puede utilizar TextEncodery TextDecoderdesde el estándar de codificación , que se completa mediante la biblioteca stringencoding , para convertir cadenas hacia y desde ArrayBuffers:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Ilmari Heikkinen avatar Jul 10 '2012 10:07 Ilmari Heikkinen

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

pero eso falla si el búfer de la matriz se vuelve demasiado grande. La mejor solución que he encontrado es usarla String.fromCharCode(null,array);y dividirla en operaciones que no acaben con la pila, pero que sean más rápidas que un solo carácter a la vez.

La mejor solución para un 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;

}

Descubrí 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.

Ryan Weinstein avatar Dec 16 '2013 06:12 Ryan Weinstein