Cómo convertir una cadena a Bytearray

90

¿Cómo puedo convertir una cadena en bytearray usando JavaScript? La salida debe ser equivalente al código C # siguiente.

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

Como UnicodeEncoding es por defecto UTF-16 con Little-Endianness.

Editar: Tengo el requisito de hacer coincidir el lado del cliente generado por bytearray con el generado en el lado del servidor usando el código C # anterior.

shas
fuente
3
javascript no es exactamente más conocido por ser fácil de usar con BLOB: ¿por qué no envía la cadena en JSON?
Marc Gravell
Tal vez puedas echar un vistazo aquí ...
V4Vendetta
2
Una cadena de Javascript es UTF-16, ¿o ya lo sabía?
Kevin
2
En primer lugar, ¿por qué necesita convertir esto en javascript?
BreakHead
17
Las cadenas no están codificadas. Sí, internamente se representan como bytes y tienen una codificación, pero eso esencialmente no tiene sentido a nivel de scripting. Las cadenas son colecciones lógicas de caracteres. Para codificar un carácter, debe elegir explícitamente un esquema de codificación, que puede utilizar para transformar cada código de carácter en una secuencia de uno o más bytes. Las respuestas a esta pregunta a continuación son basura, ya que llaman charCodeAt y colocan su valor en una matriz llamada "bytes". ¡Hola! charCodeAt puede devolver valores superiores a 255, por lo que no es un byte.
Triynko

Respuestas:

21

En C # ejecutando esto

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

Creará una matriz con

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

byte array

Para un carácter cuyo código sea mayor que 255, se verá así

byte array

Si desea un comportamiento muy similar en JavaScript, puede hacer esto (v2 es una solución un poco más robusta, mientras que la versión original solo 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
fuente
1
Ya probé esto, pero esto me da un resultado diferente al del código C # anterior. Como en este caso, la matriz de bytes de salida del código C # es = 72,0,101,0,108,0,108,0,111,0 Tengo el requisito de coincidir con ambos para que eso no funcione.
sha
2
@shas Probé la versión anterior solo en Firefox 4. La versión actualizada se probó en Firefox 4, Chrome 13 e IE9.
BrunoLM
40
Tenga en cuenta que si la cadena contiene caracteres Unicode, charCodeAt (i) será> 255, que probablemente no sea lo que desea.
broofa
23
Sí, esto es incorrecto. charCodeAt no devuelve un byte. No tiene sentido insertar un valor mayor que 255 en una matriz llamada "bytes"; muy engañoso. Esta función no realiza codificación en absoluto, solo pega los códigos de caracteres en una matriz.
Triynko
1
No entiendo por qué esta respuesta está marcada como correcta ya que no codifica nada.
AB
32

Si está buscando una solución que funcione en node.js, puede usar esto:

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
fuente
3
Esto es para node.js pero creo que la pregunta es buscar una solución que funcione en un navegador. Sin embargo, funciona correctamente, a diferencia de la mayoría de las otras respuestas a esta pregunta, así que +1.
Daniel Cassidy
Esto funciona, pero el código mucho más simple es la función convertString (myString) {var myBuffer = new Buffer (myString, 'utf16le'); console.log (myBuffer); return myBuffer; }
Philip Rutovitz
16

Supongo que C # y Java producen matrices de bytes iguales. Si tiene caracteres que no son ASCII, no es suficiente agregar un 0 adicional. Mi ejemplo contiene algunos caracteres especiales:

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

No sé si C # coloca BOM (Byte Order Marks), pero si usa UTF-16, Java String.getBytesagrega los siguientes bytes: 254255.

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:

Se agregó un carácter especial (U + 1D11E) SÍMBOLO MUSICAL G CLEF (fuera de BPM, por lo que no solo toma 2 bytes en UTF-16, sino 4.

Las versiones actuales de JavaScript utilizan "UCS-2" internamente, por lo que este símbolo ocupa el espacio de 2 caracteres normales.

No estoy seguro, pero al usar charCodeAt parece que obtenemos exactamente los puntos de código sustitutos que también se usan en UTF-16, por lo que los caracteres que no son BPM se manejan correctamente.

Este problema no es absolutamente trivial. Puede depender de las versiones y motores de JavaScript utilizados. Entonces, si desea soluciones confiables, debería echar un vistazo a:

hgoebl
fuente
1
Aún no es una respuesta completa. UTF16 es una codificación de longitud variable que utiliza fragmentos de 16 bits para representar caracteres. Un solo carácter se codificará como 2 bytes o 4 bytes, dependiendo de qué tan grande sea el valor del código del carácter. Dado que esta función escribe como máximo 2 bytes, no puede manejar todos los puntos de código de caracteres Unicode y no es una implementación completa de la codificación UTF16, ni mucho menos.
Triynko
@Triynko después de mi edición y prueba, ¿todavía crees que esta no es la respuesta completa? Si es así, ¿tiene una respuesta?
hgoebl
2
@Triynko Tiene la mitad de razón, pero en realidad esta respuesta funciona correctamente. Las cadenas de JavaScript no son en realidad secuencias de puntos de código Unicode, son secuencias de unidades de código UTF-16. A pesar del nombre, charCodeAtdevuelve una unidad de código UTF-16, en el rango 0-65535. Los caracteres fuera del rango de 2 bytes se representan como pares sustitutos, al igual que en UTF-16. (Por cierto, esto es cierto para las cadenas en varios otros lenguajes, incluidos Java y C #.)
Daniel Cassidy
Por cierto, (charCode & 0xFF00) >> 8es redundante, no es necesario enmascararlo antes de cambiar.
Patrick Roberts
15

La forma más fácil en 2018 debería ser TextEncoder, pero el elemento devuelto no es una matriz de bytes, es Uint8Array. (Y no todos los navegadores lo admiten)

let utf8Encode = new TextEncoder();
utf8Encode.encode("eee")
> Uint8Array [ 101, 101, 101 ]
code4j
fuente
Esto es peculiar. No supongo que el uso de diferentes nombres de variables como utf8Decode y utf8Encode funcionaría.
Unihedron
Puede utilizar TextDecoder de decodificación: new TextDecoder().decode(new TextEncoder().encode(str)) == str.
Fons
Aquí están las tablas de apoyo de TextEncoder: caniuse
Fons
11

Matriz de bytes UTF-16

JavaScript codifica cadenas como UTF-16 , al igual que C # UnicodeEncoding, por lo que las matrices de bytes deben coincidir exactamente usando charCodeAt()y dividiendo cada par de bytes devueltos en 2 bytes separados, como en:

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

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

Sin embargo, si desea obtener una matriz de bytes UTF-8, debe transcodificar los bytes.

Matriz de bytes UTF-8

La solución no parece trivial, pero utilicé el siguiente código en un entorno de producción de alto tráfico con gran éxito ( fuente original ).

Además, para el lector interesado, publiqué mis ayudantes Unicode que me ayudan a trabajar con longitudes de cadenas informadas por otros lenguajes 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
fuente
y ¿cuál es la inversa de esto?
simbo1905
Describiría la función inversa como "convertir una matriz de bytes UTF-8 en una cadena UTF-16 nativa". Nunca produje lo inverso. En myc env, eliminé este código cambiando la salida de la API a un rango de caracteres en lugar de un rango de bytes, luego usé runas para analizar los rangos.
jchook
Sugeriría que esta debería ser la respuesta aceptada para esta pregunta.
LeaveTheCapital
10

Inspirado en la respuesta de @ hgoebl. Su código es para UTF-16 y necesitaba algo para US-ASCII. Entonces, aquí hay una respuesta más completa que cubre US-ASCII, UTF-16 y 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 es de longitud variable y no está incluido porque tendría que escribir la codificación yo mismo. UTF-8 y UTF-16 son de longitud variable. UTF-8, UTF-16 y UTF-32 tienen un número mínimo de bits como lo indica su nombre. Si un carácter UTF-32 tiene un punto de código de 65, significa que hay 3 ceros a la izquierda. Pero el mismo código para UTF-16 solo tiene 1 0 a la izquierda. Por otro lado, US-ASCII tiene un ancho fijo de 8 bits, lo que significa que se puede traducir directamente a bytes.

String.prototype.charCodeAtdevuelve un número máximo de 2 bytes y coincide exactamente con UTF-16. Sin embargo, String.prototype.codePointAtse necesita UTF-32, que es parte de la propuesta ECMAScript 6 (Harmony). Debido a que charCodeAt devuelve 2 bytes, que son más caracteres posibles de los que puede representar US-ASCII, la función stringToAsciiByteArrayarrojará en tales casos en lugar de dividir el carácter por la mitad y tomar uno o ambos bytes.

Tenga en cuenta que esta respuesta no es trivial porque la codificación de caracteres no es trivial. El tipo de matriz de bytes que desee dependerá de la codificación de caracteres que desee que representen esos bytes.

javascript tiene la opción de usar internamente UTF-16 o UCS-2, pero como tiene métodos que actúan como si fuera UTF-16, no veo por qué cualquier navegador usaría UCS-2. Ver también: https://mathiasbynens.be/notes/javascript-encoding

Sí, sé que la pregunta tiene 4 años, pero necesitaba esta respuesta para mí.

SkySpiral7
fuente
Los resultados de Node's Buffer para '02'son [ 48, 0, 50, 0 ]donde stringToUtf16ByteArrayregresa su función [ 0, 48, 0, 50 ]. cual es la correcta?
pkyeck
@pkyeck Mi función stringToUtf16ByteArray anterior devuelve UTF-16 BE sin BOM. El ejemplo que dio del nodo es UTF-16 LE sin BOM. Pensé que Big-endian era más normal que little-endian, pero podría estar equivocado.
SkySpiral7
2

Como no puedo comentar la respuesta, me basaría en la respuesta 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);

diciendo que puede usar esto si desea usar un búfer Node.js en su navegador.

https://github.com/feross/buffer

Por lo tanto, la objeción de Tom Stickel no es válida y la respuesta es de hecho una respuesta válida.

mmdts
fuente
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
fuente
4
Sería útil si proporciona algún texto que acompañe al código y explique por qué se puede elegir este enfoque en lugar de una de las otras respuestas.
NightOwl888
este enfoque es más simple que otros pero hace lo mismo, esa es la razón por la que no escribí nada.
Fabio Maciel
encodeHexdevolverá una matriz de números de 16 bits, no bytes.
Pavlo
0

La mejor solución que se me ocurrió en el acto (aunque lo más probable es que sea cruda) sería:

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

Aunque noto que esta pregunta ha estado aquí por más de un año.

Whosdr
fuente
2
Esto no funciona correctamente. La lógica de caracteres de longitud variable es incorrecta, no hay caracteres de 8 bits en UTF-16. A pesar del nombre, charCodeAtdevuelve una unidad de código UTF-16 de 16 bits, por lo que no necesita ninguna lógica de longitud variable. Puede simplemente llamar a charCodeAt, dividir el resultado en dos bytes de 8 bits y colocarlos en la matriz de salida (primero el byte de orden más bajo, ya que la pregunta solicita UTF-16LE).
Daniel Cassidy
0

Sé que la pregunta tiene casi 4 años, pero esto es lo que funcionó sin problemas conmigo:

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

o, si desea trabajar solo con cadenas y sin Array, puede 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
fuente
2
Este tipo de trabajo funciona, pero es extremadamente engañoso. La bytesmatriz no contiene 'bytes', contiene números de 16 bits, que representan la cadena en unidades de código UTF-16. Esto es casi lo que pedía la pregunta, pero en realidad solo por accidente.
Daniel Cassidy
-1

Aquí está la misma función que @BrunoLM publicó convertida en una función de prototipo de cadena:

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

Si define la función como tal, puede llamar al método .getBytes () en cualquier cadena:

var str = "Hello World!";
var bytes = str.getBytes();
mweaver
fuente
31
Esto sigue siendo incorrecto, al igual que la respuesta a la que hace referencia. charCodeAt no devuelve un byte. No tiene sentido insertar un valor mayor que 255 en una matriz llamada "bytes"; muy engañoso. Esta función no realiza codificación en absoluto, simplemente pega los códigos de caracteres en una matriz. Para realizar la codificación UTF16, debe examinar el código de carácter, decidir si necesitará representarlo con 2 bytes o 4 bytes (ya que UTF16 es una codificación de longitud variable) y luego escribir cada byte en la matriz individualmente.
Triynko
8
Además, es una mala práctica modificar el prototipo de tipos de datos nativos.
Andrew Lundin
@AndrewLundin, eso es interesante ... ¿quién dice?
Jerther
2
@Jerther: stackoverflow.com/questions/14034180/…
Andrew Lundin
-3

No necesita guión bajo, solo use el mapa integrado:

var string = 'Hello World!';

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

Christian Gutiérrez Sierra
fuente
1
Esto devuelve una matriz de números de 16 bits que representan la cadena como una secuencia de puntos de código UTF-16. Eso no es lo que pidió el OP, pero al menos lo lleva a uno en el camino.
Daniel Cassidy