Existe uma técnica comumente aceita para converter eficientemente seqüências de JavaScript em ArrayBuffers e vice-versa? Especificamente, eu gostaria de poder escrever o conteúdo de um ArrayBuffer localStorage
e lê-lo novamente.
264
Int8Array
ArrayBufferView
, pode ser possível simplesmente usar a notação entre colchetes para copiar caracteresstring[i] = buffer[i]
e vice-versa.Uint16Array
s para caracteres de 16 bits de JS), mas as strings JavaScript são imutáveis, portanto você não pode atribuir diretamente a uma posição de caractere. Eu ainda precisaria copiarString.fromCharCode(x)
cada valor noUint16Array
normalArray
e depois chamar.join()
oArray
.string += String.fromCharCode(buffer[i]);
. Parece estranho que não haja métodos internos para converter entre seqüências de caracteres e matrizes digitadas. Eles tinham que saber que algo assim iria surgir.Respostas:
Atualização 2016 - cinco anos depois, agora existem novos métodos nas especificações (consulte o suporte abaixo) para converter entre cadeias e matrizes digitadas usando a codificação adequada.
TextEncoder
O
TextEncoder
representa :Nota de alteração desde que o texto acima foi escrito: (ibid.)
*) Especificações atualizadas (W3) e aqui (whatwg).
Depois de criar uma instância do,
TextEncoder
ele pegará uma string e a codificará usando um determinado parâmetro de codificação:É claro que você usa o
.buffer
parâmetro no resultadoUint8Array
para converter a subjacênciaArrayBuffer
em uma visualização diferente, se necessário.Apenas certifique-se de que os caracteres na sequência sigam o esquema de codificação, por exemplo, se você usar caracteres fora do intervalo UTF-8 no exemplo, eles serão codificados para dois bytes em vez de um.
Para uso geral, você usaria a codificação UTF-16 para coisas assim
localStorage
.TextDecoder
Da mesma forma, o processo oposto usa
TextDecoder
:Todos os tipos de decodificação disponíveis podem ser encontrados aqui .
A biblioteca MDN StringView
Uma alternativa para isso é usar a
StringView
biblioteca (licenciada como lgpl-3.0), cujo objetivo é:dando muito mais flexibilidade. No entanto, seria necessário vincular ou incorporar essa biblioteca enquanto
TextEncoder
/TextDecoder
está sendo incorporado em navegadores modernos.Apoio, suporte
Em julho / 2018:
TextEncoder
(Experimental, Na faixa padrão)fonte
var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};
assim você pode apenasvar array = encoder.encode('hello');
TextEncoder
é que, se você possui dados binários em uma string (como imagem), não deseja usarTextEncoder
(aparentemente). Caracteres com pontos de código maiores que 127 produzem dois bytes. Por que tenho dados binários em uma string?cy.fixture(NAME, 'binary')
(cypress
) produz uma string.Embora as soluções de Dennis e gengkev do uso do Blob / FileReader funcionem, eu não sugeriria adotar essa abordagem. É uma abordagem assíncrona para um problema simples e é muito mais lenta que uma solução direta. Fiz uma postagem no html5rocks com uma solução mais simples e (muito mais rápida): http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
E a solução é:
EDITAR:
A API de codificação ajuda a resolver o problema de conversão de cadeias . Confira a resposta de Jeff Posnik em Html5Rocks.com ao artigo original acima.
Excerto:
fonte
This is a cool text!
20 bytes em UTF8 - 40 bytes em Unicode. (2)ÄÖÜ
6 bytes em UTF8 - 6 bytes em Unicode. (3)☐☑☒
9 bytes em UTF8 - 6 bytes em Unicode. Se você deseja armazenar a sequência como arquivo UTF8 (via Blob e API do File Writer), não pode usar esses 2 métodos, porque o ArrayBuffer estará em Unicode e não em UTF8.String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).length
funciona para mim no Chrome, mas se você usar 246301 em vez disso, eu recebo sua exceção RangeErrorVocê pode usar
TextEncoder
eTextDecoder
do padrão Encoding , que é preenchido com polifólio pela biblioteca de codificação de string, para converter a string de e para ArrayBuffers:fonte
npm install text-encoding
,var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;
. Não, obrigado.Blob é muito mais lento que
String.fromCharCode(null,array);
mas isso falhará se o buffer da matriz ficar muito grande. A melhor solução que encontrei é usá
String.fromCharCode(null,array);
-lo e dividi-lo em operações que não explodem a pilha, mas são mais rápidas que um único caractere por vez.A melhor solução para buffer de matriz grande é:
Eu achei que era cerca de 20 vezes mais rápido do que usar blob. Também funciona para cordas grandes de mais de 100mb.
fonte
Com base na resposta de gengkev, criei funções para os dois lados, porque o BlobBuilder pode lidar com String e ArrayBuffer:
e
Um teste simples:
fonte
a[y * w + x] = (x + y) / 2 * 16;
eu tenteigetBlob("x")
, com muitos tipos diferentes de mimetismo - sem sorte.new BlobBuilder(); bb.append(buf);
paranew Blob([buf])
, converta o ArrayBuffer na segunda função para um UintArray vianew UintArray(buf)
(ou o que for apropriado para o tipo de dados subjacente) e depois se livre dasgetBlob()
chamadas. Por fim, para limpeza, renomeie bb para blob porque não é mais um BlobBuilder.O que se segue é sobre como obter cadeias binárias de buffers de matriz
Eu recomendo não usar
porque isso
Maximum call stack size exceeded
erro no buffer de 120000 bytes (Chrome 29))Se você precisar exatamente de uma solução síncrona, use algo como
é tão lento quanto o anterior, mas funciona corretamente. Parece que, no momento em que escrevemos isso, não há solução síncrona muito rápida para esse problema (todas as bibliotecas mencionadas neste tópico usam a mesma abordagem para seus recursos síncronos).
Mas o que eu realmente recomendo é usar a abordagem
Blob
+FileReader
a única desvantagem (não para todos) é que é assíncrona . E é cerca de 8 a 10 vezes mais rápido que as soluções anteriores! (Alguns detalhes: a solução síncrona no meu ambiente levou 950-1050 ms para o buffer de 2,4 Mb, mas a solução com o FileReader teve tempos entre 100 e 120 ms para a mesma quantidade de dados. E eu testei as duas soluções síncronas no buffer de 100 KB e elas fizeram quase ao mesmo tempo, portanto, o loop não é muito mais lento que o uso de 'aplicar'.)
BTW aqui: Como converter ArrayBuffer para e de String, o autor compara duas abordagens como eu e obtém resultados completamente opostos ( o código de teste está aqui ) Por que resultados tão diferentes? Provavelmente por causa de sua cadeia de teste com 1 KB de comprimento (ele a chamou de "veryLongStr"). Meu buffer era uma imagem JPEG muito grande do tamanho 2.4Mb.
fonte
( Atualização Por favor, veja a segunda metade desta resposta, onde eu espero que tenha fornecido uma solução mais completa.)
Também deparei com esse problema, o seguinte funciona para mim no FF 6 (para uma direção):
Infelizmente, é claro que você acaba com representações de texto ASCII dos valores na matriz, em vez de caracteres. Ainda (deve ser) muito mais eficiente que um loop, no entanto. por exemplo. Para o exemplo acima, o resultado é
0004000000
, em vez de vários caracteres nulos e um chr (4).Editar:
Depois de examinar o MDC aqui , você pode criar um a
ArrayBuffer
partir de um daArray
seguinte maneira:Para responder à sua pergunta original, isso permite converter
ArrayBuffer
<-> daString
seguinte maneira:Por conveniência, aqui está um
function
para converter um Unicode brutoString
em umArrayBuffer
(só funcionará com caracteres ASCII / um byte)As opções acima permitem que você vá de
ArrayBuffer
->String
& de volta paraArrayBuffer
onde a cadeia pode ser armazenada, por exemplo..localStorage
:)Espero que isto ajude,
Dan
fonte
Diferentemente das soluções aqui, eu precisava converter para / de dados UTF-8. Para esse propósito, codifiquei as duas funções a seguir, usando o truque (un) escape / (en) decodeURIComponent. Eles são um grande desperdício de memória, alocando 9 vezes o comprimento da utf8-string codificada, embora essas devam ser recuperadas pelo gc. Só não os use para textos de 100 MB.
Verificando se funciona:
fonte
Caso você tenha dados binários em uma string (obtida de
nodejs
+readFile(..., 'binary')
, oucypress
+cy.fixture(..., 'binary')
, etc), não poderá usarTextEncoder
. Ele suporta apenasutf8
. Bytes com valores>= 128
são transformados em 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"
fonte
Eu descobri que tinha problemas com essa abordagem, basicamente porque estava tentando gravar a saída em um arquivo e ele não estava codificado corretamente. Como o JS parece usar a codificação UCS-2 ( origem , origem ), precisamos estender mais esta solução, aqui está minha solução aprimorada que funciona para mim.
Não tive dificuldades com o texto genérico, mas quando se tratava de árabe ou coreano, o arquivo de saída não tinha todos os caracteres, mas exibia caracteres de erro
Saída do arquivo:
","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}
Peguei as informações da solução de dennis e encontrei este post .
Aqui está o meu código:
Isso me permite salvar o conteúdo em um arquivo sem problemas de codificação.
Como funciona: Basicamente, pega os pedaços de 8 bytes que compõem um caractere UTF-8 e os salva como caracteres únicos (portanto, um caractere UTF-8 construído dessa maneira pode ser composto por 1 a 4 desses caracteres). UTF-8 codifica caracteres em um formato que varia de 1 a 4 bytes de comprimento. O que fazemos aqui é codificar a picada em um componente URI e, em seguida, pegar esse componente e convertê-lo no caractere de 8 bytes correspondente. Dessa forma, não perdemos as informações fornecidas pelos caracteres UTF8 com mais de 1 byte de comprimento.
fonte
Se você usou um exemplo de matriz enorme, pode usar
arr.length=1000000
esse código para evitar problemas de retorno de chamada da pilhafunção reversa resposta mangini de cima
fonte
Bem, aqui está uma maneira um tanto complicada de fazer a mesma coisa:
Edit: BlobBuilder há muito tempo foi preterido em favor do construtor Blob, que não existia quando escrevi este post. Aqui está uma versão atualizada. (E sim, essa sempre foi uma maneira muito boba de fazer a conversão, mas foi apenas por diversão!)
fonte
Depois de jogar com a solução da mangini para converter de
ArrayBuffer
paraString
-ab2str
(que é a mais elegante e útil que encontrei - obrigado!), Tive alguns problemas ao lidar com matrizes grandes. Mais especificamente, a chamadaString.fromCharCode.apply(null, new Uint16Array(buf));
gera um erro:arguments array passed to Function.prototype.apply is too large
.Para resolvê-lo (desvio), decidi manipular a entrada
ArrayBuffer
em pedaços. Portanto, a solução modificada é:O tamanho do pedaço é definido como
2^16
porque esse foi o tamanho que eu encontrei para trabalhar no meu cenário de desenvolvimento. Definir um valor mais alto fez com que o mesmo erro ocorresse novamente. Pode ser alterado definindo aCHUNK_SIZE
variável para um valor diferente. É importante ter um número par.Nota sobre desempenho - não fiz nenhum teste de desempenho para esta solução. No entanto, como é baseado na solução anterior e pode lidar com matrizes grandes, não vejo razão para não usá-lo.
fonte
Veja aqui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (uma interface semelhante a C para seqüências de caracteres com base na interface JavaScript ArrayBuffer)
fonte
fonte
arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
Para node.js e também para navegadores usando https://github.com/feross/buffer
Nota: As soluções aqui não funcionaram para mim. Eu preciso dar suporte ao node.js e navegadores e apenas serializar o UInt8Array em uma string. Eu poderia serializá-lo como um número [], mas isso ocupa espaço desnecessário. Com essa solução, não preciso me preocupar com codificações, já que é base64. Apenas no caso de outras pessoas terem problemas com o mesmo problema ... Meus dois centavos
fonte
Digamos que você tenha um arrayBuffer binaryStr:
e então você atribui o texto ao estado.
fonte
A cadeia binária "nativa" que atob () retorna é uma matriz de 1 byte por caractere.
Portanto, não devemos armazenar 2 bytes em um personagem.
fonte
Sim:
fonte
Eu recomendo NÃO usar APIs obsoletas como o BlobBuilder
O BlobBuilder está obsoleto pelo objeto Blob. Compare o código na resposta de Dennis - onde o BlobBuilder é usado - com o código abaixo:
Observe como isso é mais limpo e menos inchado comparado ao método obsoleto ... Sim, isso é definitivamente algo a ser considerado aqui.
fonte
Consulte https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
fonte
Eu usei isso e funciona para mim.
fonte