Come convertire una stringa in Bytearray


92

Come posso convertire una stringa in bytearray usando JavaScript. L'output dovrebbe essere equivalente al codice C # seguente.

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

Poiché UnicodeEncoding è per impostazione predefinita UTF-16 con Little-Endianness.

Modifica: ho un requisito per abbinare il lato client generato da bytearray con quello generato sul lato server utilizzando il codice C # sopra.


3
javascript non è esattamente noto per essere facile da usare con i BLOB - perché non invii semplicemente la stringa in JSON?
Marc Gravell

Forse puoi dare un'occhiata qui ..
V4Vendetta

2
Una stringa Javascript è UTF-16 o lo sapevi già?
Kevin

2
Prima di tutto perché devi convertirlo in javascript?
BreakHead

17
Le stringhe non sono codificate. Sì, internamente sono rappresentati come byte e hanno una codifica, ma questo è essenzialmente privo di significato a livello di scripting. Le stringhe sono raccolte logiche di caratteri. Per codificare un carattere, è necessario scegliere esplicitamente uno schema di codifica, che è possibile utilizzare per trasformare ogni codice di carattere in una sequenza di uno o più byte. Le risposte a questa domanda di seguito sono inutili, poiché chiamano charCodeAt e inseriscono il suo valore in un array chiamato "byte". Ciao! charCodeAt può restituire valori maggiori di 255, quindi non è un byte!
Triynko

Risposte:


21

In C # in esecuzione questo

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

Creerà un array con

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

matrice di byte

Per un carattere il cui codice è maggiore di 255 sarà simile a questo

matrice di byte

Se vuoi un comportamento molto simile in JavaScript puoi farlo (v2 è una soluzione un po 'più robusta, mentre la versione originale funzionerà solo per 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(', '));


1
L'ho già provato, ma questo mi dà il risultato diverso rispetto al codice C # sopra. Come in questo caso, l'array di byte di output del codice C # è = 72,0,101,0,108,0,108,0,111,0 Ho un requisito per abbinare entrambi in modo che non funzioni.
Shas

2
@s ha provato la precedente solo su Firefox 4. La versione aggiornata è stata testata su Firefox 4, Chrome 13 e IE9.
BrunoLM

41
Nota che se la stringa contiene caratteri Unicode, charCodeAt (i) sarà> 255, che probabilmente non è quello che vuoi.
broofa

23
Sì, questo non è corretto. charCodeAt non restituisce un byte. Non ha senso inserire un valore maggiore di 255 in un array chiamato "byte"; molto fuorviante. Questa funzione non esegue affatto la codifica, ma semplicemente inserisce i codici dei caratteri in un array.
Triynko

1
Non capisco perché questa risposta sia contrassegnata come corretta poiché non codifica nulla.
AB

34

Se stai cercando una soluzione che funzioni in node.js, puoi usare questo:

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

3
Questo è per node.js ma penso che la domanda sia alla ricerca di una soluzione che funzioni in un browser. Tuttavia funziona correttamente, a differenza della maggior parte delle altre risposte a questa domanda, quindi +1.
Daniel Cassidy

Funziona, ma un codice molto più semplice è la funzione convertString (myString) {var myBuffer = new Buffer (myString, 'utf16le'); console.log (myBuffer); return myBuffer; }
Philip Rutovitz

16

Suppongo che C # e Java producano array di byte uguali. Se hai caratteri non ASCII, non è sufficiente aggiungere uno 0. Il mio esempio contiene alcuni caratteri speciali:

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

Non so se C # inserisce BOM (Byte Order Marks), ma se si utilizza UTF-16, Java String.getBytesaggiunge i seguenti byte: 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

Modificare:

Aggiunto un carattere speciale (U + 1D11E) SIMBOLO MUSICALE G CLEF (fuori BPM, quindi non solo 2 byte in UTF-16, ma 4.

Le versioni JavaScript correnti utilizzano internamente "UCS-2", quindi questo simbolo occupa lo spazio di 2 caratteri normali.

Non sono sicuro, ma quando uso charCodeAt sembra che otteniamo esattamente i codepoint surrogati utilizzati anche in UTF-16, quindi i caratteri non BPM vengono gestiti correttamente.

Questo problema è assolutamente non banale. Potrebbe dipendere dalle versioni e dai motori JavaScript utilizzati. Quindi, se desideri soluzioni affidabili, dovresti dare un'occhiata a:


1
Ancora non una risposta completa. UTF16 è una codifica a lunghezza variabile che utilizza blocchi a 16 bit per rappresentare i caratteri. Un singolo carattere verrà codificato come 2 byte o 4 byte, a seconda di quanto è grande il valore del codice del carattere. Poiché questa funzione scrive al massimo 2 byte, non è in grado di gestire tutti i punti di codice dei caratteri Unicode e non è un'implementazione completa della codifica UTF16, non di gran lunga.
Triynko

@ Triynko dopo la mia modifica e test, pensi ancora che questa non sia la risposta completa? Se sì, hai una risposta?
hgoebl

2
@Triynko Hai ragione a metà, ma in realtà questa risposta funziona correttamente. Le stringhe JavaScript non sono in realtà sequenze di punti codice Unicode, sono sequenze di unità di codice UTF-16. Nonostante il nome, charCodeAtrestituisce un'unità di codice UTF-16, nell'intervallo 0-65535. I caratteri al di fuori dell'intervallo di 2 byte sono rappresentati come coppie surrogate, proprio come in UTF-16. (A proposito, questo è vero per le stringhe in molti altri linguaggi, inclusi Java e C #.)
Daniel Cassidy

A proposito, (charCode & 0xFF00) >> 8è ridondante, non è necessario mascherarlo prima di cambiare.
Patrick Roberts

16

Il modo più semplice nel 2018 dovrebbe essere TextEncoder ma l'elemento restituito non è un array di byte, è Uint8Array. (E non tutti i browser lo supportano)

let utf8Encode = new TextEncoder();
utf8Encode.encode("eee")
> Uint8Array [ 101, 101, 101 ]

Questo è strano. Non suppongo che l'uso di nomi di variabili diversi come utf8Decode e utf8Encode funzionerebbe.
Unihedron

È possibile utilizzare TextDecoder per decodificare: new TextDecoder().decode(new TextEncoder().encode(str)) == str.
Fons

Ecco le tabelle di supporto di TextEncoder: caniuse
Fons

11

Array di byte UTF-16

JavaScript codifica le stringhe come UTF-16 , proprio come C # UnicodeEncoding, quindi gli array di byte devono corrispondere esattamente utilizzando charCodeAt()e suddividendo ciascuna coppia di byte restituita in 2 byte separati, come in:

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

Per esempio:

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

Tuttavia, se si desidera ottenere un array di byte UTF-8, è necessario transcodificare i byte.

UTF-8 Byte Array

La soluzione sembra in qualche modo non banale, ma ho utilizzato il codice seguente in un ambiente di produzione ad alto traffico con grande successo ( fonte originale ).

Inoltre, per il lettore interessato, ho pubblicato i miei helper Unicode che mi aiutano a lavorare con le lunghezze di stringa riportate da altri linguaggi come 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;
}

e qual è il contrario di questo?
simbo1905

Descriverei la funzione inversa come "converte un array di byte UTF-8 in una stringa UTF-16 nativa". Non ho mai prodotto l'inverso. In myc env, ho rimosso questo codice modificando l'output dell'API in un intervallo di caratteri anziché in un intervallo di byte, quindi ho usato le rune per analizzare gli intervalli.
jchook

Suggerirei che questa dovrebbe essere la risposta accettata per questa domanda.
LeaveTheCapital

10

Ispirato dalla risposta di @ hgoebl. Il suo codice è per UTF-16 e avevo bisogno di qualcosa per US-ASCII. Quindi ecco una risposta più completa che copre 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 è di lunghezza variabile e non è incluso perché dovrei scrivere la codifica da solo. UTF-8 e UTF-16 sono di lunghezza variabile. UTF-8, UTF-16 e UTF-32 hanno un numero minimo di bit come indica il nome. Se un carattere UTF-32 ha un punto di codice 65, significa che ci sono 3 0 iniziali. Ma lo stesso codice per UTF-16 ha solo 1 iniziale 0. US-ASCII d'altra parte ha una larghezza fissa di 8 bit, il che significa che può essere tradotto direttamente in byte.

String.prototype.charCodeAtrestituisce un numero massimo di 2 byte e corrisponde esattamente a UTF-16. Tuttavia per UTF-32 String.prototype.codePointAtè necessario che fa parte della proposta ECMAScript 6 (Harmony). Poiché charCodeAt restituisce 2 byte che sono più possibili caratteri di quanto US-ASCII possa rappresentare, la funzione stringToAsciiByteArraylancerà in questi casi invece di dividere il carattere a metà e prendere uno o entrambi i byte.

Nota che questa risposta non è banale perché la codifica dei caratteri non è banale. Il tipo di array di byte che desideri dipende dalla codifica dei caratteri che desideri che quei byte rappresentino.

javascript ha la possibilità di utilizzare internamente UTF-16 o UCS-2 ma poiché ha metodi che si comportano come se fosse UTF-16 non vedo perché nessun browser dovrebbe utilizzare UCS-2. Vedi anche: https://mathiasbynens.be/notes/javascript-encoding

Sì, lo so che la domanda è di 4 anni ma avevo bisogno di questa risposta per me stesso.


I risultati del buffer del nodo per '02'sono [ 48, 0, 50, 0 ]dove stringToUtf16ByteArrayrestituisce la funzione [ 0, 48, 0, 50 ]. quale è corretto?
pkyeck

@pkyeck La mia funzione stringToUtf16ByteArray sopra restituisce UTF-16 BE senza BOM. L'esempio fornito da node è UTF-16 LE senza BOM. Pensavo che il Big Endian fosse più normale del Little Endian, ma potevo sbagliarmi.
SkySpiral7

2

Poiché non posso commentare la risposta, mi baserei sulla risposta di 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);

dicendo che potresti usarlo se vuoi usare un buffer Node.js nel tuo browser.

https://github.com/feross/buffer

Pertanto, l'obiezione di Tom Stickel non è valida e la risposta è effettivamente una risposta valida.


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

String.prototype.decodeHex = function () {    
    return this.map(e => String.fromCharCode(e)).join('')
};

4
Sarebbe utile fornire del testo da abbinare al codice per spiegare perché si potrebbe scegliere questo approccio piuttosto che una delle altre risposte.
NightOwl888

questo approccio è più semplice di altri ma fai lo stesso, ecco perché non ho scritto nulla.
Fabio Maciel

encodeHexrestituirà un array di numeri a 16 bit, non byte.
Pavlo

0

La migliore soluzione che ho trovato sul posto (anche se molto probabilmente grezza) sarebbe:

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

Anche se ho notato che questa domanda è qui da oltre un anno.


2
Questo non funziona correttamente. La logica dei caratteri a lunghezza variabile non è corretta, non ci sono caratteri a 8 bit in UTF-16. Nonostante il nome, charCodeAtrestituisce un'unità di codice UTF-16 a 16 bit, quindi non è necessaria alcuna logica a lunghezza variabile. Puoi semplicemente chiamare charCodeAt, dividere il risultato in due byte a 8 bit e inserirli nell'array di output (primo byte di ordine più basso poiché la domanda richiede UTF-16LE).
Daniel Cassidy

0

So che la domanda ha quasi 4 anni, ma questo è ciò che ha funzionato senza problemi con me:

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

oppure, se vuoi lavorare solo con stringhe e senza Array, puoi usare:

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


2
Questo tipo di lavoro funziona, ma è estremamente fuorviante. L' bytesarray non contiene "byte", contiene numeri a 16 bit, che rappresentano la stringa in unità di codice UTF-16. Questo è quasi quello che chiedeva la domanda, ma in realtà solo per caso.
Daniel Cassidy

-1

Ecco la stessa funzione pubblicata da @BrunoLM convertita in una funzione di prototipo di stringa:

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

Se definisci la funzione come tale, puoi chiamare il metodo .getBytes () su qualsiasi stringa:

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

31
Questo è ancora errato, proprio come la risposta a cui fa riferimento. charCodeAt non restituisce un byte. Non ha senso inserire un valore maggiore di 255 in un array chiamato "byte"; molto fuorviante. Questa funzione non esegue affatto la codifica, ma semplicemente inserisce i codici dei caratteri in un array. Per eseguire la codifica UTF16, è necessario esaminare il codice del carattere, decidere se sarà necessario rappresentarlo con 2 byte o 4 byte (poiché UTF16 è una codifica a lunghezza variabile), quindi scrivere individualmente ogni byte nell'array.
Triynko

8
Inoltre, è una cattiva pratica modificare il prototipo dei tipi di dati nativi.
Andrew Lundin

@ AndrewLundin, è interessante ... dice chi?
Jerther


-3

Non è necessario il carattere di sottolineatura, basta usare la mappa incorporata:

var string = 'Hello World!';

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


1
Ciò restituisce una matrice di numeri a 16 bit che rappresentano la stringa come una sequenza di punti di codice UTF-16. Non è quello che ha chiesto l'OP, ma almeno ti fa arrivare a metà strada.
Daniel Cassidy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.