¿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 localStorage
y volver a leerlo.
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 localStorage
y volver a leerlo.
Int8Array
ArrayBufferView
, es posible que simplemente puedas usar la notación de corchetes para copiar caracteresstring[i] = buffer[i]
y viceversa.Uint16Array
s 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 copiarString.fromCharCode(x)
cada valor en elUint16Array
normalArray
y luego llamar.join()
alArray
.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.Respuestas:
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
TextEncoder
representa :Nota de cambio ya que lo anterior fue escrito: (ibid.)
*) 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:Luego, por supuesto, utiliza el
.buffer
parámetro en el resultadoUint8Array
para convertir la capa subyacenteArrayBuffer
a 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 utiliza
TextDecoder
:Todos los tipos de decodificación disponibles se pueden encontrar aquí .
La biblioteca MDN StringView
Una alternativa a esto es usar la
StringView
biblioteca (con licencia como lgpl-3.0) cuyo objetivo es:dando mucha más flexibilidad. Sin embargo, requeriría que vinculemos o incrustemos esta biblioteca mientras
TextEncoder
/TextDecoder
se está incorporando en navegadores modernos.Apoyo
A partir de julio / 2018:
TextEncoder
(Experimental, en pista estándar)fuente
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');
TextEncoder
es que si tiene datos binarios en una cadena (como, imagen), no desea usarTextEncoder
(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.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:
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:
fuente
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.String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).length
funciona para mí en Chrome, pero si usas 246301, obtengo tu excepción RangeErrorPuede usar
TextEncoder
yTextDecoder
desde el estándar de codificación , que se rellena con la biblioteca stringencoding , para convertir cadenas hacia y desde ArrayBuffers:fuente
npm install text-encoding
,var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;
. No, gracias.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:
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.
fuente
Basado en la respuesta de gengkev, creé funciones para ambos sentidos, porque BlobBuilder puede manejar String y ArrayBuffer:
y
Una prueba simple:
fuente
a[y * w + x] = (x + y) / 2 * 16;
lo he intentadogetBlob("x")
, con muchos tipos de mime diferentes, sin suerte.new BlobBuilder(); bb.append(buf);
anew Blob([buf])
, convierta el ArrayBuffer en la segunda función en un UintArray a través denew UintArray(buf)
(o lo que sea apropiado para el tipo de datos subyacente), y luego elimine lasgetBlob()
llamadas. Finalmente, para mayor limpieza, cambie el nombre de bb a blob porque ya no es un BlobBuilder.Todo lo siguiente se trata de obtener cadenas binarias de buffers de matriz
Recomiendo no usar
porque
Maximum call stack size exceeded
error en el buffer de 120000 bytes (Chrome 29))Si exactamente necesita una solución sincrónica, use algo como
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
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.
fuente
( 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):
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
ArrayBuffer
de laArray
siguiente manera:Para responder a su pregunta original, esto le permite convertir
ArrayBuffer
<-> de laString
siguiente manera:Por conveniencia, aquí hay una
function
para convertir un Unicode sin procesarString
a unArrayBuffer
(solo funcionará con caracteres ASCII / de un byte)Lo anterior le permite ir de
ArrayBuffer
->String
y volver aArrayBuffer
otra vez, donde la cadena puede almacenarse en, por ejemplo..localStorage
:)Espero que esto ayude,
Dan
fuente
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.
Comprobando que funciona:
fuente
En caso de que tenga datos binarios en una cadena (obtenidos de
nodejs
+readFile(..., 'binary')
, ocypress
+cy.fixture(..., 'binary')
, etc.), no puede usarlosTextEncoder
. Solo es compatibleutf8
. Los bytes con valores>= 128
se convierten cada uno en 2 bytes.ES2015:
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
"ºRFl¶é (÷ LõÎW0 Náò8ìÉPPv \ 0"
fuente
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:
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.
fuente
si utilizó un ejemplo de matriz enorme, puede usar
arr.length=1000000
este código para evitar problemas de devolución de llamada de pilafunción inversa respuesta mangini desde arriba
fuente
Bueno, aquí hay una forma algo complicada de hacer lo mismo:
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!)
fuente
Después de jugar con la solución de mangini para convertir de
ArrayBuffer
aString
-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
ArrayBuffer
en fragmentos. Entonces la solución modificada es:El tamaño del fragmento se establece en
2^16
porque 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.
fuente
Ver aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (una interfaz tipo C para cadenas basadas en la interfaz JavaScript ArrayBuffer)
fuente
fuente
arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
Para node.js y también para navegadores que usan https://github.com/feross/buffer
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
fuente
Digamos que tiene un arrayBuffer binaryStr:
y luego asignas el texto al estado.
fuente
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.
fuente
Si:
fuente
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:
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í.
fuente
Ver https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
fuente
Usé esto y funciona para mí.
fuente