Como converter uma String em Bytearray

92

Como posso converter uma string em bytearray usando JavaScript. A saída deve ser equivalente ao código C # abaixo.

UnicodeEncoding encoding = new UnicodeEncoding();
byte[] bytes = encoding.GetBytes(AnyString);

Como UnicodeEncoding é por padrão UTF-16 com Little-Endianness.

Edit: Eu tenho um requisito para combinar o lado do cliente gerado por bytearray com aquele gerado no lado do servidor usando o código C # acima.

shas
fonte
3
javascript não é exatamente mais conhecido por ser fácil de usar com BLOBs - por que você simplesmente não envia a string em JSON?
Marc Gravell
Talvez você possa dar uma olhada aqui ..
V4Vendetta
2
Uma string Javascript é UTF-16 ou você já sabia disso?
Kevin
2
Em primeiro lugar, por que você precisa converter isso em javascript?
BreakHead
17
Strings não são codificados. Sim, internamente eles são representados como bytes e têm uma codificação, mas isso é essencialmente sem sentido no nível de script. Strings são coleções lógicas de caracteres. Para codificar um caractere, você deve escolher explicitamente um esquema de codificação, que pode ser usado para transformar cada código de caractere em uma sequência de um ou mais bytes. As respostas a esta pergunta abaixo são lixo, pois chamam charCodeAt e colocam seu valor em uma matriz chamada "bytes". Olá! charCodeAt pode retornar valores maiores que 255, portanto, não é um byte!
Triynko

Respostas:

21

Em C # executando este

UnicodeEncoding encoding = new UnicodeEncoding();
byte[] bytes = encoding.GetBytes("Hello");

Irá criar uma matriz com

72,0,101,0,108,0,108,0,111,0

matriz de bytes

Para um caractere cujo código é maior que 255, será semelhante a este

matriz de bytes

Se você quiser um comportamento muito semelhante em JavaScript, pode fazer isso (v2 é uma solução um pouco mais robusta, enquanto a versão original só funcionará para 0x00 ~ 0xff)

var str = "Hello竜";
var bytes = []; // char codes
var bytesv2 = []; // char codes

for (var i = 0; i < str.length; ++i) {
  var code = str.charCodeAt(i);
  
  bytes = bytes.concat([code]);
  
  bytesv2 = bytesv2.concat([code & 0xff, code / 256 >>> 0]);
}

// 72, 101, 108, 108, 111, 31452
console.log('bytes', bytes.join(', '));

// 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 220, 122
console.log('bytesv2', bytesv2.join(', '));

BrunoLM
fonte
1
Eu já tentei fazer isso, mas isso me dá um resultado diferente do código C # acima. Como neste caso, a matriz de bytes de saída do código C # é = 72,0,101,0,108,0,108,0,111,0 Eu tenho um requisito para corresponder a ambos, então isso não está funcionando.
shas
2
@shas Eu testei o anterior apenas no Firefox 4. A versão atualizada foi testada no Firefox 4, Chrome 13 e IE9.
BrunoLM
41
Observe que, se a string contiver caracteres Unicode, charCodeAt (i) será> 255, o que provavelmente não é o que você deseja.
Broofa
23
Sim, isso está incorreto. charCodeAt não retorna um byte. Não faz sentido colocar um valor maior que 255 em uma matriz chamada "bytes"; muito enganador. Esta função não realiza a codificação, apenas insere os códigos dos caracteres em um array.
Triynko
1
Não entendo por que esta resposta está marcada como correta, uma vez que não codifica nada.
AB
34

Se você está procurando uma solução que funcione em node.js, você pode usar isto:

var myBuffer = [];
var str = 'Stack Overflow';
var buffer = new Buffer(str, 'utf16le');
for (var i = 0; i < buffer.length; i++) {
    myBuffer.push(buffer[i]);
}

console.log(myBuffer);
Jin
fonte
3
Isso é para node.js, mas acho que a questão é procurar uma solução que funcione em um navegador. No entanto, ele funciona corretamente, ao contrário da maioria das outras respostas a esta pergunta, então +1.
Daniel Cassidy,
Isso funciona, mas um código muito mais simples é a função convertString (myString) {var myBuffer = new Buffer (myString, 'utf16le'); console.log (meuBuffer); return myBuffer; }
Philip Rutovitz
16

Suponho que C # e Java produzam matrizes de bytes iguais. Se você tiver caracteres não ASCII, não é suficiente adicionar um 0. Meu exemplo contém alguns caracteres especiais:

var str = "Hell ö € Ω 𝄞";
var bytes = [];
var charCode;

for (var i = 0; i < str.length; ++i)
{
    charCode = str.charCodeAt(i);
    bytes.push((charCode & 0xFF00) >> 8);
    bytes.push(charCode & 0xFF);
}

alert(bytes.join(' '));
// 0 72 0 101 0 108 0 108 0 32 0 246 0 32 32 172 0 32 3 169 0 32 216 52 221 30

Não sei se C # coloca BOM (Byte Order Marks), mas se estiver usando UTF-16, Java String.getBytesadiciona os seguintes bytes: 254 255.

String s = "Hell ö € Ω ";
// now add a character outside the BMP (Basic Multilingual Plane)
// we take the violin-symbol (U+1D11E) MUSICAL SYMBOL G CLEF
s += new String(Character.toChars(0x1D11E));
// surrogate codepoints are: d834, dd1e, so one could also write "\ud834\udd1e"

byte[] bytes = s.getBytes("UTF-16");
for (byte aByte : bytes) {
    System.out.print((0xFF & aByte) + " ");
}
// 254 255 0 72 0 101 0 108 0 108 0 32 0 246 0 32 32 172 0 32 3 169 0 32 216 52 221 30

Editar:

Adicionado um caractere especial (U + 1D11E) MUSICAL SYMBOL G CLEF (fora do BPM, tomando não apenas 2 bytes em UTF-16, mas 4.

As versões atuais do JavaScript usam "UCS-2" internamente, portanto, este símbolo ocupa o espaço de 2 caracteres normais.

Não tenho certeza, mas ao usá- charCodeAtlo parece que obtemos exatamente os pontos de código substitutos também usados ​​em UTF-16, portanto, caracteres não BPM são tratados corretamente.

Este problema é absolutamente não trivial. Pode depender das versões e mecanismos de JavaScript usados. Portanto, se você deseja soluções confiáveis, deve dar uma olhada em:

hgoebl
fonte
1
Ainda não é uma resposta completa. UTF16 é uma codificação de comprimento variável que usa blocos de 16 bits para representar caracteres. Um único caractere será codificado como 2 bytes ou 4 bytes, dependendo do tamanho do valor do código do charcter. Como essa função grava no máximo 2 bytes, ela não pode lidar com todos os pontos de código de caracteres Unicode e não é uma implementação completa da codificação UTF16, nem de longe.
Triynko
@Triynko após minha edição e teste, você ainda acha que esta não é a resposta completa? Se sim, você tem uma resposta?
hgoebl
2
@Triynko Você está meio certo, mas na verdade esta resposta funciona corretamente. As strings de JavaScript não são sequências de pontos de código Unicode, são sequências de unidades de código UTF-16. Apesar do nome, charCodeAtretorna uma Unidade de Código UTF-16, no intervalo 0-65535. Os caracteres fora do intervalo de 2 bytes são representados como pares substitutos, assim como em UTF-16. (A propósito, isso é verdade para strings em várias outras linguagens, incluindo Java e C #.)
Daniel Cassidy
A propósito, (charCode & 0xFF00) >> 8é redundante, você não precisa mascará-lo antes de mudar.
Patrick Roberts
16

A maneira mais fácil em 2018 deve ser TextEncoder, mas o elemento retornado não é a matriz de bytes, é Uint8Array. (E nem todos os navegadores o suportam)

let utf8Encode = new TextEncoder();
utf8Encode.encode("eee")
> Uint8Array [ 101, 101, 101 ]
code4j
fonte
Isso é peculiar. Não suponho que usar nomes de variáveis ​​diferentes como utf8Decode e utf8Encode funcionaria.
Unihedron
Você pode usar TextDecoder para decodificar: new TextDecoder().decode(new TextEncoder().encode(str)) == str.
Fons
Aqui estão as tabelas de suporte de TextEncoder: caniuse
Fons
11

Matriz de bytes UTF-16

JavaScript codifica strings como UTF-16 , assim como C # UnicodeEncoding, então as matrizes de bytes devem corresponder exatamente usando charCodeAt()e dividindo cada par de bytes retornado em 2 bytes separados, como em:

function strToUtf16Bytes(str) {
  const bytes = [];
  for (ii = 0; ii < str.length; ii++) {
    const code = str.charCodeAt(ii); // x00-xFFFF
    bytes.push(code & 255, code >> 8); // low, high
  }
  return bytes;
}

Por exemplo:

strToUtf16Bytes('🌵'); 
// [ 60, 216, 53, 223 ]

No entanto, se você deseja obter uma matriz de bytes UTF-8, deve transcodificar os bytes.

Matriz de bytes UTF-8

A solução parece um tanto não trivial, mas usei o código abaixo em um ambiente de produção de alto tráfego com grande sucesso ( fonte original ).

Além disso, para o leitor interessado, publiquei meus ajudantes unicode que me ajudam a trabalhar com comprimentos de string relatados por outras linguagens como PHP.

/**
 * Convert a string to a unicode byte array
 * @param {string} str
 * @return {Array} of bytes
 */
export function strToUtf8Bytes(str) {
  const utf8 = [];
  for (let ii = 0; ii < str.length; ii++) {
    let charCode = str.charCodeAt(ii);
    if (charCode < 0x80) utf8.push(charCode);
    else if (charCode < 0x800) {
      utf8.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
    } else if (charCode < 0xd800 || charCode >= 0xe000) {
      utf8.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
    } else {
      ii++;
      // Surrogate pair:
      // UTF-16 encodes 0x10000-0x10FFFF by subtracting 0x10000 and
      // splitting the 20 bits of 0x0-0xFFFFF into two halves
      charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (str.charCodeAt(ii) & 0x3ff));
      utf8.push(
        0xf0 | (charCode >> 18),
        0x80 | ((charCode >> 12) & 0x3f),
        0x80 | ((charCode >> 6) & 0x3f),
        0x80 | (charCode & 0x3f),
      );
    }
  }
  return utf8;
}
jchook
fonte
e qual é o inverso disso?
simbo1905
Eu descreveria a função inversa como "converter uma matriz de bytes UTF-8 em uma string UTF-16 nativa". Nunca produzi o inverso. Em myc env, removi esse código alterando a saída da API para um intervalo de caracteres em vez de um intervalo de bytes, então usei runas para analisar os intervalos.
jchook de
Eu sugeriria que esta deveria ser a resposta aceita para esta pergunta.
LeaveTheCapital
10

Inspirado pela resposta de @hgoebl. Seu código é para UTF-16 e eu precisava de algo para US-ASCII. Portanto, aqui está uma resposta mais completa cobrindo US-ASCII, UTF-16 e UTF-32.

/**@returns {Array} bytes of US-ASCII*/
function stringToAsciiByteArray(str)
{
    var bytes = [];
   for (var i = 0; i < str.length; ++i)
   {
       var charCode = str.charCodeAt(i);
      if (charCode > 0xFF)  // char > 1 byte since charCodeAt returns the UTF-16 value
      {
          throw new Error('Character ' + String.fromCharCode(charCode) + ' can\'t be represented by a US-ASCII byte.');
      }
       bytes.push(charCode);
   }
    return bytes;
}
/**@returns {Array} bytes of UTF-16 Big Endian without BOM*/
function stringToUtf16ByteArray(str)
{
    var bytes = [];
    //currently the function returns without BOM. Uncomment the next line to change that.
    //bytes.push(254, 255);  //Big Endian Byte Order Marks
   for (var i = 0; i < str.length; ++i)
   {
       var charCode = str.charCodeAt(i);
       //char > 2 bytes is impossible since charCodeAt can only return 2 bytes
       bytes.push((charCode & 0xFF00) >>> 8);  //high byte (might be 0)
       bytes.push(charCode & 0xFF);  //low byte
   }
    return bytes;
}
/**@returns {Array} bytes of UTF-32 Big Endian without BOM*/
function stringToUtf32ByteArray(str)
{
    var bytes = [];
    //currently the function returns without BOM. Uncomment the next line to change that.
    //bytes.push(0, 0, 254, 255);  //Big Endian Byte Order Marks
   for (var i = 0; i < str.length; i+=2)
   {
       var charPoint = str.codePointAt(i);
       //char > 4 bytes is impossible since codePointAt can only return 4 bytes
       bytes.push((charPoint & 0xFF000000) >>> 24);
       bytes.push((charPoint & 0xFF0000) >>> 16);
       bytes.push((charPoint & 0xFF00) >>> 8);
       bytes.push(charPoint & 0xFF);
   }
    return bytes;
}

UTF-8 tem comprimento variável e não está incluído porque eu mesmo teria que escrever a codificação. UTF-8 e UTF-16 são de comprimento variável. UTF-8, UTF-16 e UTF-32 têm um número mínimo de bits como seu nome indica. Se um caractere UTF-32 tiver um ponto de código de 65, isso significa que há 3 zeros à esquerda. Mas o mesmo código para UTF-16 tem apenas 1 inicial de 0. US-ASCII, por outro lado, tem largura fixa de 8 bits, o que significa que pode ser traduzido diretamente em bytes.

String.prototype.charCodeAtretorna um número máximo de 2 bytes e corresponde exatamente a UTF-16. Porém, para UTF-32 String.prototype.codePointAté necessário que faz parte da proposta ECMAScript 6 (Harmony). Como charCodeAt retorna 2 bytes, que são mais caracteres possíveis do que US-ASCII pode representar, a função stringToAsciiByteArraylançará nesses casos em vez de dividir o caractere ao meio e pegar um ou ambos os bytes.

Observe que essa resposta não é trivial porque a codificação de caracteres não é trivial. O tipo de array de bytes que você deseja depende de qual codificação de caracteres você deseja que esses bytes representem.

javascript tem a opção de usar internamente UTF-16 ou UCS-2, mas como possui métodos que agem como se fossem UTF-16, não vejo por que qualquer navegador usaria UCS-2. Veja também: https://mathiasbynens.be/notes/javascript-encoding

Sim, eu sei que a pergunta é de 4 anos, mas eu precisava dessa resposta para mim.

SkySpiral7
fonte
Resultados de buffer do nó para '02'se [ 48, 0, 50, 0 ]onde, como seus stringToUtf16ByteArrayfunção retorna [ 0, 48, 0, 50 ]. qual está correto?
pkyeck de
@pkyeck Minha função stringToUtf16ByteArray acima retorna UTF-16 BE sem BOM. O exemplo que você deu do nó é UTF-16 LE sem BOM. Achei que Big-endian fosse mais normal do que little endian, mas poderia estar errado.
SkySpiral7
2

Já que não posso comentar sobre a resposta, eu usaria a resposta de Jin Izzraeel

var myBuffer = [];
var str = 'Stack Overflow';
var buffer = new Buffer(str, 'utf16le');
for (var i = 0; i < buffer.length; i++) {
    myBuffer.push(buffer[i]);
}

console.log(myBuffer);

dizendo que você poderia usar isso se quiser usar um buffer Node.js em seu navegador.

https://github.com/feross/buffer

Portanto, a objeção de Tom Stickel não é válida, e a resposta é de fato uma resposta válida.

mmdts
fonte
1
String.prototype.encodeHex = function () {
    return this.split('').map(e => e.charCodeAt())
};

String.prototype.decodeHex = function () {    
    return this.map(e => String.fromCharCode(e)).join('')
};
Fabio Maciel
fonte
4
Seria útil se você fornecer algum texto para acompanhar o código para explicar por que alguém pode escolher essa abordagem em vez de uma das outras respostas.
NightOwl888 de
essa abordagem é mais simples do que outras, mas faça o mesmo, por isso não escrevi nada.
Fabio Maciel de
encodeHexretornará uma matriz de números de 16 bits, não bytes.
Pavlo
0

A melhor solução que eu encontrei no local (embora provavelmente bruta) seria:

String.prototype.getBytes = function() {
    var bytes = [];
    for (var i = 0; i < this.length; i++) {
        var charCode = this.charCodeAt(i);
        var cLen = Math.ceil(Math.log(charCode)/Math.log(256));
        for (var j = 0; j < cLen; j++) {
            bytes.push((charCode << (j*8)) & 0xFF);
        }
    }
    return bytes;
}

Embora eu note que essa pergunta está aqui há mais de um ano.

Whosdr
fonte
2
Isso não funciona corretamente. A lógica dos caracteres de comprimento variável está incorreta, não há caracteres de 8 bits em UTF-16. Apesar do nome, charCodeAtretorna uma unidade de código UTF-16 de 16 bits, portanto, você não precisa de nenhuma lógica de comprimento variável. Você pode simplesmente chamar charCodeAt, dividir o resultado em dois bytes de 8 bits e colocá-los na matriz de saída (byte de ordem mais baixa primeiro, pois a questão pede UTF-16LE).
Daniel Cassidy,
0

Eu sei que a pergunta tem quase 4 anos, mas isto é o que funcionou bem comigo:

String.prototype.encodeHex = function () {
  var bytes = [];
  for (var i = 0; i < this.length; ++i) {
    bytes.push(this.charCodeAt(i));
  }
  return bytes;
};

Array.prototype.decodeHex = function () {    
  var str = [];
  var hex = this.toString().split(',');
  for (var i = 0; i < hex.length; i++) {
    str.push(String.fromCharCode(hex[i]));
  }
  return str.toString().replace(/,/g, "");
};

var str = "Hello World!";
var bytes = str.encodeHex();

alert('The Hexa Code is: '+bytes+' The original string is:  '+bytes.decodeHex());

ou, se quiser trabalhar apenas com strings e sem Array, você pode usar:

String.prototype.encodeHex = function () {
  var bytes = [];
  for (var i = 0; i < this.length; ++i) {
    bytes.push(this.charCodeAt(i));
  }
  return bytes.toString();
};

String.prototype.decodeHex = function () {    
  var str = [];
  var hex = this.split(',');
  for (var i = 0; i < hex.length; i++) {
    str.push(String.fromCharCode(hex[i]));
  }
  return str.toString().replace(/,/g, "");
};

var str = "Hello World!";
var bytes = str.encodeHex();

alert('The Hexa Code is: '+bytes+' The original string is:  '+bytes.decodeHex());

Hasan A Yousef
fonte
2
Isso funciona, mas é extremamente enganoso. A bytesmatriz não contém 'bytes', ela contém números de 16 bits, que representam a string em unidades de código UTF-16. Isso é quase o que a pergunta pedia, mas na verdade apenas por acidente.
Daniel Cassidy
-1

Aqui está a mesma função que @BrunoLM postou convertida em uma função de protótipo String:

String.prototype.getBytes = function () {
  var bytes = [];
  for (var i = 0; i < this.length; ++i) {
    bytes.push(this.charCodeAt(i));
  }
  return bytes;
};

Se você definir a função como tal, poderá chamar o método .getBytes () em qualquer string:

var str = "Hello World!";
var bytes = str.getBytes();
Mweaver
fonte
31
Isso ainda está incorreto, assim como a resposta a que se refere. charCodeAt não retorna um byte. Não faz sentido colocar um valor maior que 255 em uma matriz chamada "bytes"; muito enganador. Esta função não realiza a codificação, apenas insere os códigos dos caracteres em um array. Para realizar a codificação UTF16, você deve examinar o código de caracteres, decidir se precisará representá-lo com 2 bytes ou 4 bytes (já que UTF16 é uma codificação de comprimento variável) e, a seguir, gravar cada byte no array individualmente.
Triynko
8
Além disso, é uma prática ruim modificar o protótipo de tipos de dados nativos.
Andrew Lundin
@AndrewLundin, isso é interessante ... diz quem?
Jerther
2
@Jerther: stackoverflow.com/questions/14034180/…
Andrew Lundin
-3

Você não precisa de sublinhado, apenas use o mapa integrado:

var string = 'Hello World!';

document.write(string.split('').map(function(c) { return c.charCodeAt(); }));

Christian Gutierrez Sierra
fonte
1
Isso retorna uma matriz de números de 16 bits que representam a string como uma sequência de pontos de código UTF-16. Não foi isso que o OP pediu, mas pelo menos você chega lá.
Daniel Cassidy