Comment convertir une chaîne en Bytearray

90

Comment puis-je convertir une chaîne dans bytearray en utilisant JavaScript. La sortie doit être équivalente au code C # ci-dessous.

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

Comme UnicodeEncoding est par défaut UTF-16 avec Little-Endianness.

Edit: J'ai une exigence pour faire correspondre le côté client généré par bytearray avec celui généré côté serveur en utilisant le code C # ci-dessus.

shas
la source
3
javascript n'est pas exactement connu pour être facile à utiliser avec les BLOB - pourquoi ne pas simplement envoyer la chaîne en JSON?
Marc Gravell
Peut-être que vous pouvez jeter un oeil ici ..
V4Vendetta
2
Une chaîne Javascript est UTF-16, ou le saviez-vous déjà?
Kevin le
2
Tout d'abord, pourquoi avez-vous besoin de convertir cela en javascript?
BreakHead
17
Les chaînes ne sont pas codées. Oui, en interne, ils sont représentés sous forme d'octets et ils ont un encodage, mais cela n'a essentiellement aucun sens au niveau du script. Les chaînes sont des collections logiques de caractères. Pour coder un caractère, vous devez explicitement choisir un schéma de codage, que vous pouvez utiliser pour transformer chaque code de caractère en une séquence d'un ou plusieurs octets. Les réponses à cette question ci-dessous sont des déchets, car ils appellent charCodeAt et collent sa valeur dans un tableau appelé "bytes". salut! charCodeAt peut renvoyer des valeurs supérieures à 255, donc ce n'est pas un octet!
Triynko

Réponses:

21

En C # exécutant ceci

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

Créera un tableau avec

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

tableau d'octets

Pour un caractère dont le code est supérieur à 255, il ressemblera à ceci

tableau d'octets

Si vous voulez un comportement très similaire en JavaScript, vous pouvez le faire (la v2 est une solution un peu plus robuste, tandis que la version originale ne fonctionnera que pour 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
la source
1
J'ai déjà essayé cela mais cela me donne un résultat différent de celui du code C # ci-dessus. Comme pour ce cas, le tableau d'octets de sortie de code C # est = 72,0,101,0,108,0,108,0,111,0 J'ai une exigence pour faire correspondre les deux donc cela ne fonctionne pas.
shas
2
@shas j'ai testé la précédente uniquement sur Firefox 4. La version mise à jour a été testée sur Firefox 4, Chrome 13 et IE9.
BrunoLM
40
Notez que si la chaîne contient des caractères unicode, charCodeAt (i) sera> 255, ce qui n'est probablement pas ce que vous voulez.
broofa
23
Ouais, c'est incorrect. charCodeAt ne renvoie pas d'octet. Cela n'a aucun sens de pousser une valeur supérieure à 255 dans un tableau appelé "octets"; très trompeur. Cette fonction n'effectue aucun encodage, elle colle simplement les codes de caractères dans un tableau.
Triynko
1
Je ne comprends pas pourquoi cette réponse est marquée comme correcte car elle n'encode rien.
AB
32

Si vous recherchez une solution qui fonctionne dans node.js, vous pouvez utiliser ceci:

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
la source
3
C'est pour node.js mais je pense que la question est de chercher une solution qui fonctionne dans un navigateur. Néanmoins, cela fonctionne correctement, contrairement à la plupart des autres réponses à cette question, donc +1.
Daniel Cassidy
Cela fonctionne mais le code beaucoup plus simple est la fonction convertString (myString) {var myBuffer = new Buffer (myString, 'utf16le'); console.log (myBuffer); return myBuffer; }
Philip Rutovitz
16

Je suppose que C # et Java produisent des tableaux d'octets égaux. Si vous avez des caractères non ASCII, il ne suffit pas d'ajouter un 0 supplémentaire. Mon exemple contient quelques caractères spéciaux:

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

Je ne sais pas si C # place BOM (Byte Order Marks), mais si vous utilisez UTF-16, Java String.getBytesajoute les octets suivants: 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

Éditer:

Ajout d'un caractère spécial (U + 1D11E) SYMBOLE MUSICAL G CLEF (en dehors de BPM, prenant donc non seulement 2 octets en UTF-16, mais 4.

Les versions actuelles de JavaScript utilisent "UCS-2" en interne, donc ce symbole prend l'espace de 2 caractères normaux.

Je ne suis pas sûr, mais lors de son utilisation, charCodeAtil semble que nous obtenons exactement les points de code de substitution également utilisés dans UTF-16, de sorte que les caractères non BPM sont gérés correctement.

Ce problème est absolument non trivial. Cela peut dépendre des versions et des moteurs JavaScript utilisés. Donc, si vous voulez des solutions fiables, vous devriez jeter un œil à:

hgoebl
la source
1
Pas encore une réponse complète. UTF16 est un codage de longueur variable qui utilise des blocs de 16 bits pour représenter les caractères. Un seul caractère sera codé sur 2 ou 4 octets, selon la taille de la valeur du code du caractère. Étant donné que cette fonction écrit au plus 2 octets, elle ne peut pas gérer tous les points de code de caractères Unicode et n'est pas une implémentation complète du codage UTF16, pas de loin.
Triynko
@Triynko après ma modification et mon test, pensez-vous toujours que ce n'est pas la réponse complète? Si oui, avez-vous une réponse?
hgoebl
2
@Triynko Vous avez à moitié raison, mais en fait cette réponse fonctionne correctement. Les chaînes JavaScript ne sont pas en fait des séquences de points de code Unicode, ce sont des séquences d'unités de code UTF-16. Malgré le nom, charCodeAtrenvoie une unité de code UTF-16, comprise entre 0 et 65535. Les caractères en dehors de la plage de 2 octets sont représentés comme des paires de substitution, tout comme en UTF-16. (À propos, cela est vrai pour les chaînes dans plusieurs autres langues, y compris Java et C #.)
Daniel Cassidy
Au fait, (charCode & 0xFF00) >> 8c'est redondant, vous n'avez pas besoin de le masquer avant de changer de vitesse.
Patrick Roberts
15

Le moyen le plus simple en 2018 devrait être TextEncoder mais l'élément renvoyé n'est pas un tableau d'octets, c'est Uint8Array. (Et tous les navigateurs ne le prennent pas en charge)

let utf8Encode = new TextEncoder();
utf8Encode.encode("eee")
> Uint8Array [ 101, 101, 101 ]
code4j
la source
C'est étrange. Je ne suppose pas que l'utilisation de noms de variables différents comme utf8Decode et utf8Encode fonctionnerait.
Unihedron
Vous pouvez utiliser TextDecoder à décoder: new TextDecoder().decode(new TextEncoder().encode(str)) == str.
Fons le
Voici les tableaux de support de TextEncoder: caniuse
Fons
11

Tableau UTF-16 octets

JavaScript encode les chaînes en UTF-16 , tout comme C # UnicodeEncoding, de sorte que les tableaux d'octets doivent correspondre exactement en utilisant charCodeAt()et en divisant chaque paire d'octets retournée en 2 octets séparés, comme dans:

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

Par exemple:

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

Cependant, si vous souhaitez obtenir un tableau d'octets UTF-8, vous devez transcoder les octets.

Tableau UTF-8 octets

La solution semble quelque peu non triviale, mais j'ai utilisé le code ci-dessous dans un environnement de production à fort trafic avec beaucoup de succès ( source d'origine ).

Aussi, pour le lecteur intéressé, j'ai publié mes helpers unicode qui m'aident à travailler avec des longueurs de chaîne rapportées par d'autres langages tels que 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
la source
et quel est l'inverse de cela?
simbo1905
Je décrirais la fonction inverse comme "convertir un tableau d'octets UTF-8 en une chaîne UTF-16 native". Je n'ai jamais produit l'inverse. Dans myc env, j'ai supprimé ce code en modifiant la sortie de l'API en une plage de caractères au lieu d'une plage d'octets, puis j'ai utilisé des runes pour analyser les plages.
jchook
Je suggérerais que ce devrait être la réponse acceptée pour cette question.
LeaveTheCapital
10

Inspiré par la réponse de @ hgoebl. Son code est pour UTF-16 et j'avais besoin de quelque chose pour US-ASCII. Voici donc une réponse plus complète couvrant US-ASCII, UTF-16 et 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 est de longueur variable et n'est pas inclus car je devrais écrire l'encodage moi-même. UTF-8 et UTF-16 sont de longueur variable. UTF-8, UTF-16 et UTF-32 ont un nombre minimum de bits comme leur nom l'indique. Si un caractère UTF-32 a un point de code de 65, cela signifie qu'il y a 3 0 non significatifs. Mais le même code pour UTF-16 n'a qu'un seul 0 en tête. L'US-ASCII, en revanche, est de largeur fixe de 8 bits, ce qui signifie qu'il peut être directement traduit en octets.

String.prototype.charCodeAtrenvoie un nombre maximum de 2 octets et correspond exactement à UTF-16. Cependant pour UTF-32 String.prototype.codePointAtest nécessaire qui fait partie de la proposition ECMAScript 6 (Harmony). Étant donné que charCodeAt renvoie 2 octets, ce qui est plus de caractères possibles que l'US-ASCII ne peut représenter, la fonction stringToAsciiByteArraylancera dans de tels cas au lieu de diviser le caractère en deux et de prendre l'un ou les deux octets.

Notez que cette réponse n'est pas triviale car le codage des caractères n'est pas trivial. Le type de tableau d'octets souhaité dépend du codage de caractères que vous souhaitez que ces octets représentent.

javascript a la possibilité d'utiliser en interne UTF-16 ou UCS-2, mais comme il a des méthodes qui agissent comme UTF-16, je ne vois pas pourquoi un navigateur utiliserait UCS-2. Voir aussi: https://mathiasbynens.be/notes/javascript-encoding

Oui, je sais que la question a 4 ans mais j'avais besoin de cette réponse pour moi-même.

SkySpiral7
la source
Les résultats du tampon de nœud pour '02'sont [ 48, 0, 50, 0 ]où votre stringToUtf16ByteArrayfonction retourne [ 0, 48, 0, 50 ]. laquelle est correcte?
pkyeck
@pkyeck Ma fonction stringToUtf16ByteArray ci-dessus renvoie UTF-16 BE sans BOM. L'exemple que vous avez donné à partir du nœud est UTF-16 LE sans BOM. J'avais pensé que Big-endian était plus normal que little-endian mais cela pouvait se tromper.
SkySpiral7
2

Puisque je ne peux pas commenter la réponse, je miserais sur la réponse 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);

en disant que vous pouvez l'utiliser si vous souhaitez utiliser un tampon Node.js dans votre navigateur.

https://github.com/feross/buffer

Par conséquent, l'objection de Tom Stickel n'est pas valable et la réponse est en effet une réponse valable.

mmdts
la source
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
la source
4
Il serait utile que vous fournissiez un texte pour accompagner le code pour expliquer pourquoi on pourrait choisir cette approche plutôt que l'une des autres réponses.
NightOwl888
cette approche est plus simple que d'autres mais faites de même, c'est la raison pour laquelle je n'ai rien écrit.
Fabio Maciel
encodeHexrenverra un tableau de nombres 16 bits, pas d'octets.
Pavlo
0

La meilleure solution que j'ai trouvée sur place (bien que probablement brute) serait:

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

Bien que je remarque que cette question est là depuis plus d'un an.

Whosdr
la source
2
Cela ne fonctionne pas correctement. La logique des caractères de longueur variable est incorrecte, il n'y a pas de caractères 8 bits en UTF-16. Malgré le nom, charCodeAtrenvoie une unité de code UTF-16 16 bits, vous n'avez donc pas besoin de logique de longueur variable. Vous pouvez simplement appeler charCodeAt, diviser le résultat en deux octets de 8 bits et les insérer dans le tableau de sortie (octet de poids faible en premier puisque la question demande UTF-16LE).
Daniel Cassidy
0

Je sais que la question a presque 4 ans, mais c'est ce qui a fonctionné sans problème avec moi:

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, si vous souhaitez travailler uniquement avec des chaînes et pas de tableau, vous pouvez utiliser:

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
la source
2
Ce genre de travail fonctionne, mais est extrêmement trompeur. Le bytestableau ne contient pas d'octets, il contient des nombres de 16 bits, qui représentent la chaîne en unités de code UTF-16. C'est à peu près ce que la question demandait, mais vraiment par accident.
Daniel Cassidy
-1

Voici la même fonction que @BrunoLM a publiée convertie en fonction prototype String:

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

Si vous définissez la fonction en tant que telle, vous pouvez appeler la méthode .getBytes () sur n'importe quelle chaîne:

var str = "Hello World!";
var bytes = str.getBytes();
mweaver
la source
31
C'est toujours incorrect, tout comme la réponse à laquelle il fait référence. charCodeAt ne renvoie pas d'octet. Cela n'a aucun sens de pousser une valeur supérieure à 255 dans un tableau appelé "octets"; très trompeur. Cette fonction n'effectue pas du tout de codage, elle colle simplement les codes de caractères dans un tableau. Pour effectuer le codage UTF16, vous devez examiner le code du caractère, décider si vous devrez le représenter avec 2 octets ou 4 octets (puisque UTF16 est un codage de longueur variable), puis écrire chaque octet dans le tableau individuellement.
Triynko
8
De plus, il est déconseillé de modifier le prototype des types de données natifs.
Andrew Lundin
@AndrewLundin, c'est intéressant ... dit qui?
Jerther
2
@Jerther: stackoverflow.com/questions/14034180/…
Andrew Lundin
-3

Vous n'avez pas besoin de soulignement, utilisez simplement la carte intégrée:

var string = 'Hello World!';

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

Christian Gutierrez Sierra
la source
1
Cela renvoie un tableau de nombres 16 bits représentant la chaîne sous la forme d'une séquence de points de code UTF-16. Ce n'est pas ce que le PO a demandé, mais au moins, il vous permet d'y arriver.
Daniel Cassidy