Convertendo entre strings e ArrayBuffers

264

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 localStoragee lê-lo novamente.

kpozin
fonte
1
Não tenho experiência nisso, mas, a julgar pela documentação da API ( khronos.org/registry/typedarray/specs/latest ), se você criar um Int8Array ArrayBufferView, pode ser possível simplesmente usar a notação entre colchetes para copiar caracteres string[i] = buffer[i]e vice-versa.
FK82 6/08/11
2
@ FK82, que parece uma abordagem razoável (usando Uint16Arrays 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 copiar String.fromCharCode(x)cada valor no Uint16Arraynormal Arraye depois chamar .join()o Array.
kpozin
@kpozin: É verdade, realmente não pensei nisso.
FK82
5
@kpozin Acontece que a maioria dos mecanismos JS modernos otimizou a concatenação de cadeias até o ponto em que é mais barato usar apenas 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.
download
arrayBuffer.toString () está funcionando bem para mim.
citizen conn

Respostas:

128

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

A TextEncoderinterface representa um codificador para um método específico, que é uma codificação de caracteres específica, como utf-8,iso-8859-2, koi8, cp1261, gbk, ... Um codificador recebe um fluxo de pontos de código como entrada e emite um fluxo de bytes.

Nota de alteração desde que o texto acima foi escrito: (ibid.)

Nota: Firefox, Chrome e Opera costumavam ter suporte para tipos de codificação diferentes de utf-8 (como utf-16, iso-8859-2, koi8, cp1261 e gbk). No Firefox 48, [...] Chrome 54 e Opera 41, nenhum outro tipo de codificação está disponível além do utf-8, para corresponder às especificações. *

*) Especificações atualizadas (W3) e aqui (whatwg).

Depois de criar uma instância do, TextEncoderele pegará uma string e a codificará usando um determinado parâmetro de codificação:

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

É claro que você usa o .bufferparâmetro no resultado Uint8Arraypara converter a subjacência ArrayBufferem 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 usaTextDecoder :

A TextDecoderinterface representa um descodificador para um modo específico, que é uma codificação de caracteres específicos, como utf-8, iso-8859-2, koi8, cp1261, gbk, ... Um descodificador toma uma corrente de bytes como entrada e emite um fluxo de pontos de código.

Todos os tipos de decodificação disponíveis podem ser encontrados aqui .

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

A biblioteca MDN StringView

Uma alternativa para isso é usar a StringViewbiblioteca (licenciada como lgpl-3.0), cujo objetivo é:

  • criar uma interface do tipo C para seqüências de caracteres (por exemplo, uma matriz de códigos de caracteres - um ArrayBufferView em JavaScript) com base na interface JavaScript ArrayBuffer
  • para criar uma biblioteca altamente extensível que qualquer pessoa possa estender adicionando métodos ao objeto StringView.prototype
  • para criar uma coleção de métodos para esses objetos do tipo string (desde agora: stringViews) que funcionam estritamente em matrizes de números, em vez de criar novas strings JavaScript imutáveis
  • para trabalhar com codificações Unicode diferentes das DOMStrings padrão UTF-16 do JavaScript

dando muito mais flexibilidade. No entanto, seria necessário vincular ou incorporar essa biblioteca enquanto TextEncoder/ TextDecoderestá sendo incorporado em navegadores modernos.

Apoio, suporte

Em julho / 2018:

TextEncoder (Experimental, Na faixa padrão)

 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

fonte
2
Não há suporte para TextDecoder de IE & Borda: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete
1
De acordo com a MS, está em desenvolvimento: developer.microsoft.com/en-us/microsoft-edge/platform/status/…
Maurice Müller
Não há suporte para Safari Mobile (iOS) em 2018/04/18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
homem de bronze
One-liner: 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');
Yeti
1
O problema TextEncoderé que, se você possui dados binários em uma string (como imagem), não deseja usar TextEncoder(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.
X-yuri
176

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

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:

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:

A API de codificação facilita a conversão entre bytes não processados ​​e cadeias JavaScript nativas, independentemente de qual das muitas codificações padrão você precisa trabalhar.

<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
fonte
16
Infelizmente meu comentário sobre html5rocks ainda não foi aprovado. Portanto, uma resposta curta aqui. Ainda acho que esse não é o caminho certo, porque você sente falta de muitos caracteres, principalmente porque a maioria das páginas está atualmente em codificação UTF-8. Por um lado, para caracteres mais especiais (digamos asiáticos), a função charCodeAt retorna um valor de 4 bytes, para que sejam cortados. Por outro lado, caracteres simples em inglês aumentam o ArrayBuffer duas vezes (você está usando 2 bytes para cada caractere de 1 byte). Imagine enviar um texto em inglês através de um WebSocket; ele precisará duas vezes (não é bom em ambiente de tempo real).
Dennis
9
Três exemplos: (1) 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.
Dennis
3
Eu recebo um erro: RangeError não capturado: tamanho máximo da pilha de chamadas excedido. Qual poderia ser o problema?
19243 Jacob
6
@Dennis - as strings JS usam UCS2, não UTF8 (ou mesmo UTF16) - o que significa que charCodeAt () sempre retorna valores 0 -> 65535. Qualquer ponto de código UTF-8 que requer extremidades de 4 bytes será representado com pares substitutos (consulte en.wikipedia .org / wiki /… ) - ou seja, dois valores UCS2 de 16 bits separados.
broofa
6
@jacob - Acredito que o erro ocorre porque há um limite no comprimento da matriz que pode ser passado para o método apply (). Por exemplo, String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthfunciona para mim no Chrome, mas se você usar 246301 em vez disso, eu recebo sua exceção RangeError
broofa
71

Você pode usar TextEncodere TextDecoderdo padrão Encoding , que é preenchido com polifólio pela biblioteca de codificação de string, para converter a string de e para ArrayBuffers:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Ilmari Heikkinen
fonte
2
By the way, isso está disponível no Firefox por padrão: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard
2
Polegares para novas APIs, que são muito melhores do que soluções alternativas estranhas!
Tomáš Zato - Restabelece Monica
1
Isso não funcionará com todos os tipos de caracteres por aí.
David
5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Não, obrigado.
Evan Hu
resmungar ... se eu tiver um buffer de matriz existente, quero escrever uma string, acho que preciso pegar o uint8array e copiá-lo pela segunda vez?
shaunc
40

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

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;

}

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.

Ryan Weinstein
fonte
3
Devemos ir com esta solução. Como isso resolve mais um caso de uso do que o aceito
sam
24

Com base na resposta de gengkev, criei funções para os dois lados, porque o BlobBuilder pode lidar com String e 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());
}

e

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

Um teste simples:

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
fonte
Em arrayBuffer2String (), você quis chamar retorno de chamada (...) em vez de console.log ()? Caso contrário, o argumento de retorno de chamada não será utilizado.
22612 Dan Danimimore
Parece o caminho a seguir - obrigado genkev e Dennis. Parece meio bobo que não há nenhuma maneira sincronizada para alcançar este objetivo, mas o que você pode fazer ...
kpozin
JavaScript é de thread único. Portanto, o FileReader é assíncrono por dois motivos: (1) não bloqueia a execução de outro JavaScript ao carregar um arquivo (enorme) (imagine um aplicativo mais complexo) e (2) não bloqueia a interface do usuário / navegador (problema comum com código JS de longa execução). Muitas APIs são assíncronas. Mesmo no XMLHttpRequest 2, o síncrono é removido.
Dennis
Eu realmente esperava que isso funcionasse para mim, mas a conversão de string para ArrayBuffer não está funcionando de maneira confiável. Estou criando um ArrayBuffer com 256 valores e posso transformá-lo em uma seqüência de caracteres com comprimento 256. Mas, se tentar convertê-lo novamente em um ArrayBuffer - dependendo do conteúdo do meu ArrayBuffer inicial - estou obtendo 376 elementos. Se você quiser tentar reproduzir o meu problema, estou tratando meu ArrayBuffer como uma grade 16x16 em um Uint8Array, com valores calculados como a[y * w + x] = (x + y) / 2 * 16; eu tentei getBlob("x"), com muitos tipos diferentes de mimetismo - sem sorte.
Matt Cruikshank
18
O BlobBuilder foi descontinuado em navegadores mais recentes. Altere new BlobBuilder(); bb.append(buf);para new Blob([buf]), converta o ArrayBuffer na segunda função para um UintArray via new UintArray(buf)(ou o que for apropriado para o tipo de dados subjacente) e depois se livre das getBlob()chamadas. Por fim, para limpeza, renomeie bb para blob porque não é mais um BlobBuilder.
sowbug
18

O que se segue é sobre como obter cadeias binárias de buffers de matriz

Eu recomendo não usar

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

porque isso

  1. trava em grandes buffers (alguém escreveu sobre o tamanho "mágico" de 246300, mas recebi um Maximum call stack size exceedederro no buffer de 120000 bytes (Chrome 29))
  2. tem um desempenho realmente ruim (veja abaixo)

Se você precisar exatamente de uma solução síncrona, use algo como

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

é 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

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

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.

Konstantin Smolyanin
fonte
13

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

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

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 ArrayBufferpartir de um da Arrayseguinte maneira:

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 à sua pergunta original, isso permite converter ArrayBuffer<-> da Stringseguinte maneira:

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 conveniência, aqui está um functionpara converter um Unicode bruto Stringem um ArrayBuffer(só funcionará com caracteres ASCII / um 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 ]);

As opções acima permitem que você vá de ArrayBuffer-> String& de volta para ArrayBufferonde a cadeia pode ser armazenada, por exemplo. .localStorage:)

Espero que isto ajude,

Dan

Dan Phillimore
fonte
1
Eu não acho que esse seja um método eficiente (em termos de tempo ou espaço), e essa é uma maneira muito incomum de armazenar dados binários.
kpozin
@kpozin: Tanto quanto eu sei, não há nenhuma outra maneira de armazenar dados binários em localStorage
Dan Phillimore
1
Que tal usar a codificação base64?
Nick Sotiros
13

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.

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

Verificando se funciona:

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

Caso você tenha dados binários em uma string (obtida de nodejs+ readFile(..., 'binary'), ou cypress+ cy.fixture(..., 'binary'), etc), não poderá usar TextEncoder. Ele suporta apenas utf8. Bytes com valores >= 128são transformados em 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"

user3832931
fonte
7

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:

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

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.

Dieghito
fonte
6

Se você usou um exemplo de matriz enorme, pode usar arr.length=1000000 esse código para evitar problemas de retorno de chamada da pilha

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
}

função reversa resposta mangini de cima

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

Bem, aqui está uma maneira um tanto complicada de fazer a mesma coisa:

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

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

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

Depois de jogar com a solução da mangini para converter de ArrayBufferpara String- ab2str(que é a mais elegante e útil que encontrei - obrigado!), Tive alguns problemas ao lidar com matrizes grandes. Mais especificamente, a chamada String.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 ArrayBufferem pedaços. Portanto, a solução modificada é:

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

O tamanho do pedaço é definido como 2^16porque 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 a CHUNK_SIZEvariá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.

yinon
fonte
você pode usar typedarray.subarray para obter um pedaço de cada posição e tamanho especificado, isto é o que eu faço para ler cabeçalhos off formatos binários em js
Nikos M.
2

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)

pascov
fonte
2
Esse código está sob a GPLv3. Eu acho que o Mozilla não é profissional para combinar esse código com a documentação compatível com os padrões.
user239558
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
fonte
esse código é incorreto se a string contiver caracteres unicode. exemplo:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp
2

Para node.js e também para navegadores usando 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: 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

cancerbero
fonte
2

Digamos que você tenha um arrayBuffer binaryStr:

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

e então você atribui o texto ao estado.

Hilal Aissani
fonte
1

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.

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

Sim:

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

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:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

Observe como isso é mais limpo e menos inchado comparado ao método obsoleto ... Sim, isso é definitivamente algo a ser considerado aqui.

realkstrawn93
fonte
Quero dizer, sim, mas isso Blob construtor não estava de volta realmente utilizável em 2012;)
gengkev
0

Eu usei isso e funciona para mim.

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
fonte