Conversión entre cadenas y ArrayBuffers

265

¿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 localStoragey volver a leerlo.

kpozin
fuente
1
No tengo ninguna experiencia en esto, pero a juzgar por la documentación de la API ( khronos.org/registry/typedarray/specs/latest ) si construyes un Int8Array ArrayBufferView, es posible que simplemente puedas usar la notación de corchetes para copiar caracteres string[i] = buffer[i]y viceversa.
FK82
2
@ FK82, parece un enfoque razonable (usando Uint16Arrays para los caracteres de 16 bits de JS), pero las cadenas de JavaScript son inmutables, por lo que no puede asignar directamente a una posición de carácter. Todavía necesitaría copiar String.fromCharCode(x)cada valor en el Uint16Arraynormal Arrayy luego llamar .join()al Array.
kpozin
@kpozin: Cierto, realmente no lo pensé bien.
FK82
55
@kpozin Resulta que la mayoría de los motores JS modernos han optimizado la concatenación de cadenas hasta el punto en que es más barato usarla string += String.fromCharCode(buffer[i]);. Parece extraño que no haya métodos integrados para convertir entre cadenas y matrices escritas. Tenían que saber que algo así sucedería.
descargar
arrayBuffer.toString () está funcionando bien para mí.
ciudadano conn

Respuestas:

129

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.

TextEncoder

El TextEncoderrepresenta :

La TextEncoderinterfaz representa un codificador para un método específico, que 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: (ibid.)

Nota: Firefox, Chrome y Opera solían tener soporte para 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 que no sean utf-8, para que coincidan con las especificaciones. *

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

Después de crear una instancia del TextEncoder, tomará una cadena y la codificará utilizando 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"));

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 en 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 a 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 utilizaTextDecoder :

La TextDecoderinterfaz representa un decodificador para un método específico, que es una codificación de caracteres específico, como utf-8, iso-8859-2, koi8, cp1261, gbk, ... Un decodificador toma una corriente de bytes como entrada y emite una corriente de elementos 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));

La biblioteca MDN StringView

Una alternativa a esto es usar la StringViewbiblioteca (con licencia como lgpl-3.0) cuyo objetivo es:

  • para crear una interfaz similar a C para cadenas (es decir, una matriz de códigos de caracteres, una 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 tales objetos tipo cadena (desde ahora: stringViews) que funcionan estrictamente en matrices de números en lugar de crear nuevas cadenas JavaScript inmutables
  • para trabajar con codificaciones Unicode que no sean las DOMStrings UTF-16 predeterminadas de JavaScript

dando mucha más flexibilidad. Sin embargo, requeriría que vinculemos o incrustemos esta biblioteca mientras TextEncoder/ TextDecoderse está incorporando en 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

fuente
2
No hay soporte para TextDecoder de IE & Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete
1
Según MS, está en desarrollo: developer.microsoft.com/en-us/microsoft-edge/platform/status/…
Maurice Müller el
No hay soporte para Safari Mobile (ios) en 2018-04-18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
hombre de bronce
One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};para que puedas simplementevar array = encoder.encode('hello');
Yeti
1
Lo que pasa TextEncoderes que si tiene datos binarios en una cadena (como, imagen), no desea usar TextEncoder(aparentemente). Los caracteres con puntos de código superiores a 127 producen dos bytes. ¿Por qué tengo datos binarios en una cadena? cy.fixture(NAME, 'binary')( cypress) produce una cadena.
x-yuri
176

Aunque las soluciones de Dennis y gengkev del uso de Blob / FileReader funcionan, no sugeriría adoptar ese enfoque. Es un enfoque asíncrono a un problema simple, y es mucho más lento que una solución directa. He publicado 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 . Vea 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 procesar 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
fuente
16
Lamentablemente, mi comentario sobre html5rocks aún no está aprobado. Por lo tanto, una breve respuesta aquí. Sigo pensando que esta no es la forma correcta, porque extrañas muchos caracteres, especialmente porque la mayoría de las páginas están en codificación UTF-8 hoy. Por un lado, para caracteres más especiales (digamos asiáticos), la función charCodeAt devuelve un valor de 4 bytes, por lo que se cortarán. Por otro lado, los caracteres simples en inglés harán crecer el ArrayBuffer dos veces (está utilizando 2 Bytes por cada carácter de 1 Byte). Imagine que envía un texto en inglés a través de un WebSocket, necesitará el doble de tiempo (no es bueno en un entorno de tiempo real).
Dennis
9
Tres ejemplos: (1) This is a cool text!20 Byte en UTF8 - 40 Byte en Unicode. (2) ÄÖÜ6 bytes en UTF8 - 6 bytes en Unicode. (3) ☐☑☒9 bytes en UTF8 - 6 bytes en Unicode. Si desea almacenar la cadena como archivo UTF8 (a través de Blob y File Writer API), no puede usar estos 2 métodos, porque ArrayBuffer estará en Unicode y no en UTF8.
Dennis
3
Me sale un error: RangeError no capturado: se excedió el tamaño máximo de la pila de llamadas ¿Cual podría ser el problema?
Jacob
66
@Dennis: las cadenas JS usan UCS2, no UTF8 (o incluso UTF16), lo que significa que charCodeAt () siempre devuelve valores 0 -> 65535. Cualquier punto de código UTF-8 que requiera extremos de 4 bytes se representará con pares sustitutos (ver en.wikipedia .org / wiki / ... ) - es decir, dos valores UCS2 de 16 bits separados.
broofa
66
@jacob: creo que el error se debe a que hay un límite en la longitud de la matriz que se puede pasar al método apply (). Por ejemplo, String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthfunciona para mí en Chrome, pero si usas 246301, obtengo tu excepción RangeError
broofa
71

Puede usar TextEncodery TextDecoderdesde el estándar de codificación , que se rellena con 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
fuente
2
Por cierto, esto está disponible en Firefox de forma predeterminada: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard
2
¡Felicitaciones por las nuevas API que son mucho mejores que las soluciones extrañas!
Tomáš Zato - Restablece a Monica el
1
Esto no funcionará con todo tipo de personajes.
David
55
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. No, gracias.
Evan Hu
quejarse ... si tengo un buffer de matriz existente en el que quiero escribir una cadena, supongo que tengo que tomar el uint8array y copiarlo por segunda vez?
shaunc
40

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

pero eso falla si el búfer de matriz se vuelve demasiado grande. La mejor solución que he encontrado es usarlo String.fromCharCode(null,array);y dividirlo en operaciones que no exploten la pila, pero que sean más rápidas que un solo personaje 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 100mb.

Ryan Weinstein
fuente
3
Deberíamos ir con esta solución. Como esto resuelve un caso de uso más que el aceptado
sam
24

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 prueba simple:

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"
            }
        )
    }
)
Dennis
fuente
En arrayBuffer2String (), ¿quiso llamar a la devolución de llamada (...) en lugar de console.log ()? De lo contrario, el argumento de devolución de llamada no se usa.
Dan Phillimore
Este parece ser el camino a seguir: gracias genkev y Dennis. Parece un poco tonto que no haya una forma sincrónica de lograr esto, pero ¿qué puedes hacer ...
Kpozin
JavaScript es de un solo subproceso. Por lo tanto, FileReader es asíncrono por dos razones: (1) no bloqueará la ejecución de otro JavaScript mientras carga un archivo (enorme) (imagine una aplicación más compleja) y (2) no bloqueará la interfaz de usuario / navegador (problema común con código JS de larga ejecución). Muchas API son asincrónicas. Incluso en XMLHttpRequest 2 se elimina la sincronización.
Dennis
Realmente esperaba que esto funcionara para mí, pero la conversión de string a ArrayBuffer no funciona de manera confiable. Estoy haciendo un ArrayBuffer con 256 valores, y puedo convertirlo en una cadena con una longitud de 256. Pero luego, si trato de convertirlo de nuevo en un ArrayBuffer, dependiendo del contenido de mi ArrayBuffer inicial, obtengo 376 elementos. Si desea intentar reproducir mi problema, estoy tratando mi ArrayBuffer como una cuadrícula de 16x16 en un Uint8Array, con valores calculados como a[y * w + x] = (x + y) / 2 * 16; lo he intentado getBlob("x"), con muchos tipos de mime diferentes, sin suerte.
Matt Cruikshank
18
BlobBuilder está en desuso en los navegadores más nuevos. Cambie new BlobBuilder(); bb.append(buf);a new Blob([buf]), convierta el ArrayBuffer en la segunda función en un UintArray a través de new UintArray(buf)(o lo que sea apropiado para el tipo de datos subyacente), y luego elimine las getBlob()llamadas. Finalmente, para mayor limpieza, cambie el nombre de bb a blob porque ya no es un BlobBuilder.
sowbug
18

Todo lo siguiente se trata de obtener cadenas binarias de buffers de matriz

Recomiendo no usar

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

porque

  1. se bloquea en grandes buffers (alguien escribió sobre el tamaño "mágico" de 246300 pero recibí un Maximum call stack size exceedederror en el buffer de 120000 bytes (Chrome 29))
  2. tiene un rendimiento realmente pobre (ver abajo)

Si exactamente necesita 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 en el momento de escribir esto no existe una solución síncrona bastante rápida para ese problema (todas las bibliotecas mencionadas en este tema utilizan el mismo enfoque para sus características síncronas).

Pero lo que realmente recomiendo es usar el enfoque 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' }));
}

La única desventaja (no para todos) es que es asíncrono . ¡Y es aproximadamente 8-10 veces más rápido que las soluciones anteriores! (Algunos detalles: la solución síncrona en mi entorno tomó 950-1050 ms para el búfer 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 ambas soluciones síncronas en el búfer de 100Kb y han tomado casi al mismo tiempo, por lo que el bucle no es mucho más lento al usar 'aplicar'.)

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

Konstantin Smolyanin
fuente
13

( Actualización Por favor, consulte la segunda mitad de esta respuesta, donde (con suerte) he proporcionado una solución más completa).

También me encontré con este problema, lo siguiente funciona 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. Sin embargo, todavía (debería ser) mucho más eficiente que un bucle. p.ej. Para el ejemplo anterior, el resultado es 0004000000, en lugar de varios caracteres nulos y un chr (4).

Editar:

Después de buscar en MDC aquí , puede crear una ArrayBufferde la Arraysiguiente manera:

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<-> de la Stringsiguiente manera:

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 functionpara convertir un Unicode sin procesar Stringa un 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 de ArrayBuffer-> Stringy volver a ArrayBufferotra vez, donde la cadena puede almacenarse en, por ejemplo. .localStorage:)

Espero que esto ayude,

Dan

Dan Phillimore
fuente
1
No creo que este sea un método eficiente (en términos de tiempo o espacio), y esta es una forma muy inusual de almacenar datos binarios.
kpozin
@kpozin: Hasta donde yo sé, no hay otra manera de almacenar datos binarios en localStorage
Dan Phillimore
1
¿Qué pasa con el uso de la codificación base64?
Nick Sotiros
13

A diferencia de las soluciones aquí, necesitaba convertir a / desde datos UTF-8. Para este propósito, codifiqué las siguientes dos funciones, usando el truco (un) escape / (en) decodeURIComponent. Son bastante desperdiciadores de memoria, asignando 9 veces la longitud de la cadena utf8 codificada, aunque gc debería recuperarlos. Simplemente no los use para texto de 100mb.

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

Comprobando que funciona:

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

En caso de que tenga datos binarios en una cadena (obtenidos de nodejs+ readFile(..., 'binary'), o cypress+ cy.fixture(..., 'binary'), etc.), no puede usarlos TextEncoder. Solo es compatible utf8. Los bytes con valores >= 128se convierten cada uno en 2 bytes.

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"

usuario3832931
fuente
7

Descubrí que tenía problemas con este enfoque, básicamente porque estaba tratando de escribir el resultado en un archivo y no estaba codificado correctamente. Dado que JS parece usar la codificación UCS-2 ( fuente , fuente ), necesitamos ampliar esta solución un paso más, aquí está mi solución mejorada que me funciona.

No tuve dificultades con el texto genérico, pero cuando estaba en árabe o coreano, el archivo de salida no tenía todos los caracteres, sino que mostraba caracteres de error

Archivo de salida: ","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}

Tomé la información de la solución de Dennis y esta publicación que encontré.

Aquí está mi código:

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

Esto me permite guardar el contenido en un archivo sin problemas de codificación.

Cómo funciona: Básicamente, toma los fragmentos individuales de 8 bytes que componen un carácter UTF-8 y los guarda como caracteres individuales (por lo tanto, un carácter UTF-8 construido de esta manera, podría estar compuesto por 1-4 de estos caracteres). UTF-8 codifica caracteres en un formato que varía de 1 a 4 bytes de longitud. Lo que hacemos aquí es codificar la picadura en un componente URI y luego tomar este componente y traducirlo en el correspondiente carácter de 8 bytes. De esta forma no perdemos la información dada por los caracteres UTF8 que tienen más de 1 byte de longitud.

Dieghito
fuente
6

si utilizó un ejemplo de matriz enorme, puede usar arr.length=1000000 este código para evitar problemas de devolución de llamada de pila

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
}

función inversa respuesta mangini desde arriba

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

Bueno, aquí hay una forma algo complicada de hacer lo mismo:

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

Editar: BlobBuilder ha quedado en desuso en favor del constructor Blob, que no existía cuando escribí esta publicación por primera vez. Aquí hay una versión actualizada. (Y sí, esta siempre ha sido una forma muy tonta de hacer la conversión, ¡pero fue solo por diversión!)

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]));
gengkev
fuente
3

Después de jugar con la solución de mangini para convertir de ArrayBuffera String- ab2str(que es la más elegante y útil que he encontrado, ¡gracias!), Tuve algunos problemas al manejar matrices grandes. Más específicamente, llamandoString.fromCharCode.apply(null, new Uint16Array(buf)); arroja un error:

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

Para resolverlo (bypass), he decidido manejar la entrada ArrayBufferen fragmentos. Entonces la solución modificada es:

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

El tamaño del fragmento se establece en 2^16porque este era el tamaño que encontré que funcionaba en mi panorama de desarrollo. Establecer un valor más alto provocó el mismo error. Se puede modificar configurando elCHUNK_SIZE variable a un valor diferente. Es importante tener un número par.

Nota sobre el rendimiento: no realicé ninguna prueba de rendimiento para esta solución. Sin embargo, dado que se basa en la solución anterior y puede manejar matrices grandes, no veo ninguna razón para no usarla.

yinon
fuente
puede usar typedarray.subarray para obtener un fragmento en la posición y tamaño especificados, esto es lo que hago para leer encabezados de formatos binarios en js
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;
  }
Admir
fuente
este código tiene errores si la cadena contiene caracteres unicode. ejemplo:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp
2

Para node.js y también para navegadores que usan 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: Las soluciones aquí no funcionaron para mí. Necesito soportar node.js y navegadores y solo serializar UInt8Array en una cadena. Podría serializarlo como un número [] pero eso ocupa espacio innecesario. Con esa solución no necesito preocuparme por las codificaciones ya que es base64. En caso de que otras personas luchen con el mismo problema ... Mis dos centavos

cancerbero
fuente
2

Digamos que tiene un arrayBuffer binaryStr:

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

y luego asignas el texto al estado.

Hilal Aissani
fuente
1

La cadena binaria "nativa" que devuelve atob () es una matriz de 1 byte por carácter.

Por lo tanto, no deberíamos almacenar 2 bytes en un personaje.

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;
}
wdhwg001
fuente
1

Si:

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

Recomiendo NO usar API obsoletas como BlobBuilder

BlobBuilder ha sido desaprobado por el objeto Blob. Compare el código en la respuesta de Dennis, donde se usa BlobBuilder, con el siguiente código:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

Tenga en cuenta cuánto más limpio y menos hinchado se compara con el método obsoleto ... Sí, esto es definitivamente algo a tener en cuenta aquí.

realkstrawn93
fuente
Quiero decir, sí, pero ese constructor Blob no era realmente utilizable en 2012;)
gengkev
0

Usé esto y funciona para mí.

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;
}
Elias Vargas
fuente