Conversion entre chaînes et ArrayBuffers

265

Existe-t-il une technique communément acceptée pour convertir efficacement des chaînes JavaScript en ArrayBuffers et vice-versa? Plus précisément, j'aimerais pouvoir écrire le contenu d'un ArrayBuffer localStorageet le relire.

kpozin
la source
1
Je n'ai aucune expérience dans ce domaine, mais à en juger par la documentation de l'API ( khronos.org/registry/typedarray/specs/latest ) si vous en créez un, Int8Array ArrayBufferViewil pourrait être possible d'utiliser simplement la notation des crochets pour copier les caractères string[i] = buffer[i]et vice versa.
FK82
2
@ FK82, qui ressemble à une approche raisonnable (en utilisant Uint16Arrays pour les caractères 16 bits de JS), mais les chaînes JavaScript sont immuables, vous ne pouvez donc pas attribuer directement à une position de caractère. J'aurais encore besoin de copier String.fromCharCode(x)chaque valeur de la Uint16Arraydans une normale Arraypuis d'appeler .join()la Array.
kpozin
@kpozin: C'est vrai, je n'y ai pas vraiment réfléchi.
FK82
5
@kpozin Il s'avère que la plupart des moteurs JS modernes ont optimisé la concaténation des chaînes au point où il est moins cher de l'utiliser string += String.fromCharCode(buffer[i]);. Il semble étrange qu'il n'y aurait pas de méthodes intégrées pour convertir entre les chaînes et les tableaux typés. Ils devaient savoir que quelque chose comme ça arriverait.
télécharger
arrayBuffer.toString () fonctionne bien pour moi.
citizen conn

Réponses:

129

Mise à jour 2016 - cinq ans plus tard, il y a maintenant de nouvelles méthodes dans les spécifications (voir le support ci-dessous) pour convertir entre les chaînes et les tableaux typés en utilisant un encodage approprié.

TextEncoder

Le TextEncoderreprésente :

L' TextEncoderinterface représente un encodeur pour une méthode spécifique, c'est-à-dire un encodage de caractères spécifique, comme utf-8,iso-8859-2, koi8, cp1261, gbk, ... Un encodeur prend un flux de points de code en entrée et émet un flux d'octets.

Modifier la note depuis la rédaction de ce qui précède: (ibid.)

Remarque: Firefox, Chrome et Opera prenaient auparavant en charge les types d'encodage autres que utf-8 (tels que utf-16, iso-8859-2, koi8, cp1261 et gbk). Depuis Firefox 48 [...], Chrome 54 [...] et Opera 41, aucun autre type d'encodage n'est disponible autre que utf-8, afin de correspondre aux spécifications. *

*) Spécifications mises à jour (W3) et ici (whatwg).

Après avoir créé une instance de, TextEncoderil prendra une chaîne et l'encodera en utilisant un paramètre d'encodage donné:

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

Vous utilisez alors bien sûr le .bufferparamètre sur le résultat Uint8Arraypour convertir la sous-couche ArrayBufferen une vue différente si nécessaire.

Assurez-vous simplement que les caractères de la chaîne adhèrent au schéma de codage, par exemple, si vous utilisez des caractères en dehors de la plage UTF-8 dans l'exemple, ils seront codés sur deux octets au lieu d'un.

Pour une utilisation générale, vous utiliseriez le codage UTF-16 pour des choses comme localStorage.

TextDecoder

De même, le processus inverse utiliseTextDecoder :

L' TextDecoderinterface représente un décodeur pour un procédé spécifique, qui est un codage de caractères spécifique, comme utf-8, iso-8859-2, koi8, cp1261, gbk, ... Un décodeur prend un flux d'octets en entrée et émet un flux de points de code.

Tous les types de décodage disponibles peuvent être trouvés ici .

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

La bibliothèque MDN StringView

Une alternative à ceux-ci est d'utiliser la StringViewbibliothèque (sous licence lgpl-3.0) dont le but est:

  • pour créer une interface de type C pour les chaînes (c'est-à-dire un tableau de codes de caractères - un ArrayBufferView en JavaScript) basé sur l'interface JavaScript ArrayBuffer
  • pour créer une bibliothèque hautement extensible que n'importe qui peut étendre en ajoutant des méthodes à l'objet StringView.prototype
  • pour créer une collection de méthodes pour ces objets de type chaîne (depuis maintenant: stringViews) qui fonctionnent strictement sur des tableaux de nombres plutôt que sur la création de nouvelles chaînes JavaScript immuables
  • pour travailler avec des encodages Unicode autres que les DOMStrings UTF-16 par défaut de JavaScript

donnant beaucoup plus de flexibilité. Cependant, cela nous obligerait à créer un lien vers ou à intégrer cette bibliothèque pendant que TextEncoder/ TextDecoderest intégré dans les navigateurs modernes.

Soutien

En juillet / 2018:

TextEncoder (Expérimental, sur voie standard)

 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

la source
2
Pas de prise en charge de TextDecoder d'IE et Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete
1
Selon MS, il est en développement: developer.microsoft.com/en-us/microsoft-edge/platform/status/…
Maurice Müller
Pas de support pour Safari Mobile (ios) au 18/04/2018: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
bronze man
One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};pour que vous puissiez simplementvar array = encoder.encode('hello');
Yeti
1
Le truc avec, TextEncoderc'est que si vous avez des données binaires dans une chaîne (comme, image), vous ne voulez pas utiliser TextEncoder(apparemment). Les caractères avec des points de code supérieurs à 127 produisent deux octets. Pourquoi ai-je des données binaires dans une chaîne? cy.fixture(NAME, 'binary')( cypress) produit une chaîne.
x-yuri
176

Bien que les solutions Dennis et gengkev d'utilisation de Blob / FileReader fonctionnent, je ne suggérerais pas d'adopter cette approche. Il s'agit d'une approche asynchrone d'un problème simple, et elle est beaucoup plus lente qu'une solution directe. J'ai fait un post dans html5rocks avec une solution plus simple et (beaucoup plus rapide): http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

Et la solution est:

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

ÉDITER:

L' API d'encodage permet de résoudre le problème de conversion des chaînes . Découvrez la réponse de Jeff Posnik sur Html5Rocks.com à l'article original ci-dessus.

Extrait:

L'API d'encodage facilite la conversion entre les octets bruts et les chaînes JavaScript natives, quel que soit le nombre d'encodages standard avec lequel vous devez travailler.

<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
la source
16
Malheureusement, mon commentaire sur html5rocks n'est pas encore approuvé. Par conséquent, une réponse courte ici. Je pense toujours que ce n'est pas la bonne façon, car vous manquez beaucoup de caractères, surtout parce que la plupart des pages sont en encodage UTF-8 aujourd'hui. D'un côté, pour les caractères plus spéciaux (disons asiatiques), la fonction charCodeAt renvoie une valeur de 4 octets, ils seront donc coupés. De l'autre côté, les simples caractères anglais feront croître le ArrayBuffer deux fois (vous utilisez 2 octets pour chaque caractère de 1 octet). Imaginez envoyer un texte anglais sur un WebSocket, il faudra deux fois plus de temps (pas bon en environnement temps réel).
Dennis
9
Trois exemples: (1) This is a cool text!20 octets en UTF8 - 40 octets en Unicode. (2) ÄÖÜ6 octets en UTF8 - 6 octets en Unicode. (3) ☐☑☒9 octets en UTF8 - 6 octets en Unicode. Si vous souhaitez stocker la chaîne en tant que fichier UTF8 (via l'API Blob et File Writer), vous ne pouvez pas utiliser ces 2 méthodes, car l'ArrayBuffer sera en Unicode et non en UTF8.
Dennis
3
J'obtiens une erreur: RangeError non capturée: la taille maximale de la pile d'appels a été dépassée. Quel pourrait être le problème?
Jacob
6
@Dennis - Les chaînes JS utilisent UCS2, pas UTF8 (ou même UTF16) - ce qui signifie que charCodeAt () renvoie toujours des valeurs 0 -> 65535. Tout point de code UTF-8 qui nécessite des extrémités de 4 octets sera représenté par des paires de substitution (voir en.wikipedia .org / wiki /… ) - c'est-à-dire deux valeurs UCS2 16 bits distinctes.
broofa
6
@jacob - Je crois que l'erreur est due au fait qu'il y a une limite sur la longueur du tableau qui peut être passée à la méthode apply (). Par exemple, String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthfonctionne pour moi dans Chrome, mais si vous utilisez 246301 à la place, j'obtiens votre exception RangeError
broofa
71

Vous pouvez utiliser TextEncoderet à TextDecoderpartir de la norme Encoding , qui est remplie par la bibliothèque stringencoding , pour convertir la chaîne vers et depuis ArrayBuffers:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Ilmari Heikkinen
la source
2
Par ailleurs, cela est disponible dans Firefox par défaut: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard
2
Bravo pour de nouvelles API qui sont bien meilleures que des solutions de contournement étranges!
Tomáš Zato - Rétablir Monica
1
Cela ne fonctionnera pas avec tous les types de personnages.
David
5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Non merci.
Evan Hu
grogner ... si j'ai un arraybuffer existant je veux écrire une chaîne dans je suppose que je dois prendre le uint8array et le copier une 2ème fois ??
shaunc
40

Blob est beaucoup plus lent que String.fromCharCode(null,array);

mais cela échoue si le tampon du tableau devient trop grand. La meilleure solution que j'ai trouvée est de l'utiliser String.fromCharCode(null,array);et de la diviser en opérations qui ne feront pas exploser la pile, mais sont plus rapides qu'un seul caractère à la fois.

La meilleure solution pour un grand tampon de tableau est:

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;

}

J'ai trouvé que c'était environ 20 fois plus rapide que d'utiliser un blob. Il fonctionne également pour les grandes chaînes de plus de 100 Mo.

Ryan Weinstein
la source
3
Nous devrions aller avec cette solution. Comme cela résout un cas d'utilisation de plus que celui accepté
sam
24

Sur la base de la réponse de gengkev, j'ai créé des fonctions dans les deux sens, car BlobBuilder peut gérer String et 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());
}

et

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

Un test simple:

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
la source
Dans arrayBuffer2String (), vouliez-vous appeler le rappel (...) au lieu de console.log ()? Sinon, l'argument de rappel n'est pas utilisé.
Dan Phillimore
Cela ressemble à la voie à suivre - merci genkev et Dennis. Semble assez idiot qu'il n'y ait pas de moyen synchrone pour y parvenir, mais que pouvez-vous faire ...
kpozin
JavaScript est un thread unique. Par conséquent, le FileReader est asynchrone pour deux raisons: (1) il ne bloquera pas l'exécution d'autres JavaScript lors du chargement d'un (énorme) fichier (imaginez une application plus complexe) et (2) il ne bloquera pas l'interface utilisateur / le navigateur (problème courant avec un long code JS). De nombreuses API sont asynchrones. Même dans XMLHttpRequest 2, le synchrone est supprimé.
Dennis
J'espérais vraiment que cela fonctionnerait pour moi, mais la conversion de la chaîne en ArrayBuffer ne fonctionne pas de manière fiable. Je fais un ArrayBuffer avec 256 valeurs, et je peux le transformer en une chaîne de longueur 256. Mais ensuite, si j'essaye de le reconvertir en ArrayBuffer - en fonction du contenu de mon ArrayBuffer initial - je sors 376 éléments. Si vous voulez essayer de reproduire mon problème, je traite mon ArrayBuffer comme une grille 16x16 dans un Uint8Array, avec des valeurs calculées comme a[y * w + x] = (x + y) / 2 * 16; j'ai essayé getBlob("x"), avec de nombreux mimetypes différents - pas de chance.
Matt Cruikshank
18
BlobBuilder est déconseillé dans les nouveaux navigateurs. Remplacez new BlobBuilder(); bb.append(buf);par new Blob([buf]), transtypez ArrayBuffer dans la deuxième fonction en un UintArray via new UintArray(buf)(ou tout ce qui convient au type de données sous-jacent), puis supprimez les getBlob()appels. Enfin, pour la propreté, renommez bb en blob car ce n'est plus un BlobBuilder.
sowbug
18

Tout ce qui suit concerne l'obtention de chaînes binaires à partir de tampons de tableau

Je recommanderais de ne pas utiliser

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

parce qu'il

  1. se bloque sur les gros tampons (quelqu'un a écrit sur la taille "magique" de 246300 mais j'ai eu une Maximum call stack size exceedederreur sur le tampon de 120000 octets (Chrome 29))
  2. il a des performances vraiment médiocres (voir ci-dessous)

Si vous avez exactement besoin d'une solution synchrone, utilisez quelque chose comme

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

il est aussi lent que le précédent mais fonctionne correctement. Il semble qu'au moment d'écrire ceci, il n'y a pas de solution synchrone assez rapide pour ce problème (toutes les bibliothèques mentionnées dans cette rubrique utilisent la même approche pour leurs fonctionnalités synchrones).

Mais ce que je recommande vraiment, c'est d'utiliser l' approche 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' }));
}

le seul inconvénient (pas pour tous) est qu'il est asynchrone . Et c'est environ 8 à 10 fois plus rapide que les solutions précédentes! (Quelques détails: la solution synchrone sur mon environnement a pris 950-1050 ms pour un tampon de 2,4 Mo, mais la solution avec FileReader avait des temps d'environ 100-120 ms pour la même quantité de données. Et j'ai testé les deux solutions synchrones sur un tampon de 100 Ko et elles ont pris presque en même temps, donc la boucle n'est pas beaucoup plus lente en utilisant 'appliquer'.)

BTW ici: Comment convertir ArrayBuffer vers et depuis String auteur compare deux approches comme moi et obtient des résultats complètement opposés ( son code de test est ici ) Pourquoi des résultats si différents? Probablement à cause de sa chaîne de test longue de 1 Ko (il l'a appelée "veryLongStr"). Mon tampon était une très grande image JPEG de 2,4 Mo.

Konstantin Smolyanin
la source
13

( Mise à jour Veuillez consulter la deuxième moitié de cette réponse, où j'ai (espérons-le) fourni une solution plus complète.)

J'ai également rencontré ce problème, les travaux suivants pour moi dans FF 6 (pour une direction):

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

Malheureusement, bien sûr, vous vous retrouvez avec des représentations textuelles ASCII des valeurs du tableau, plutôt que des caractères. Cependant, il est (devrait être) beaucoup plus efficace qu'une boucle. par exemple. Pour l'exemple ci-dessus, le résultat est 0004000000, plutôt que plusieurs caractères nuls et un chr (4).

Éditer:

Après avoir regardé MDC ici , vous pouvez créer un à ArrayBufferpartir d'un Arraycomme suit:

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;

Pour répondre à votre question d'origine, cela vous permet de convertir ArrayBuffer<-> Stringcomme suit:

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

Pour plus de commodité, voici un functionpour convertir un Unicode brut Stringen un ArrayBuffer(ne fonctionnera qu'avec des caractères ASCII / un octet)

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

Ce qui précède vous permet de passer de ArrayBuffer-> String& back à ArrayBuffernouveau, où la chaîne peut être stockée par exemple. .localStorage:)

J'espère que cela t'aides,

Dan

Dan Phillimore
la source
1
Je ne pense pas que ce soit une méthode efficace (en termes de temps ou d'espace), et c'est une façon très inhabituelle de stocker des données binaires.
kpozin
@kpozin: Pour autant que je sache, il n'y a pas d'autre moyen de stocker des données binaires dans localStorage
Dan Phillimore
1
Qu'en est-il de l'utilisation de l'encodage base64?
Nick Sotiros
13

Contrairement aux solutions ici, j'avais besoin de convertir vers / à partir de données UTF-8. À cet effet, j'ai codé les deux fonctions suivantes, en utilisant l'astuce (un) escape / (en) decodeURIComponent. Ils gaspillent assez de mémoire, allouant 9 fois la longueur de la chaîne utf8 encodée, bien que ceux-ci devraient être récupérés par gc. Ne les utilisez tout simplement pas pour du texte de 100 Mo.

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

Vérifier que cela fonctionne:

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

Dans le cas où vous avez des données binaires dans une chaîne (obtenues à partir de nodejs+ readFile(..., 'binary'), ou cypress+ cy.fixture(..., 'binary'), etc.), vous ne pouvez pas utiliser TextEncoder. Il prend en charge uniquement utf8. Les octets avec des valeurs >= 128sont chacun transformés en 2 octets.

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
la source
7

J'ai trouvé que j'avais des problèmes avec cette approche, essentiellement parce que j'essayais d'écrire la sortie dans un fichier et qu'elle n'était pas encodée correctement. Étant donné que JS semble utiliser le codage UCS-2 ( source , source ), nous devons étendre cette solution un peu plus loin, voici ma solution améliorée qui fonctionne pour moi.

Je n'ai eu aucune difficulté avec le texte générique, mais lorsqu'il était en arabe ou en coréen, le fichier de sortie n'avait pas tous les caractères mais montrait à la place des caractères d'erreur

Sortie de fichier: ","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}

J'ai pris les informations de la solution de dennis et ce poste que j'ai trouvé.

Voici mon code:

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

Cela me permet d'enregistrer le contenu dans un fichier sans problème d'encodage.

Comment cela fonctionne: Il prend essentiellement les morceaux de 8 octets simples composant un caractère UTF-8 et les enregistre en tant que caractères uniques (par conséquent, un caractère UTF-8 construit de cette manière peut être composé de 1 à 4 de ces caractères). UTF-8 code les caractères dans un format variant de 1 à 4 octets. Ce que nous faisons ici, c'est coder la piqûre dans un composant URI, puis prendre ce composant et le traduire dans le caractère 8 octets correspondant. De cette façon, nous ne perdons pas les informations fournies par les caractères UTF8 de plus d'un octet de long.

Dieghito
la source
6

si vous avez utilisé un exemple de tableau énorme, arr.length=1000000 vous pouvez utiliser ce code pour éviter les problèmes de rappel de pile

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
}

fonction inverse mangini réponse du haut

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
la source
4

Eh bien, voici une façon quelque peu compliquée de faire la même chose:

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 a longtemps été déconseillé en faveur du constructeur Blob, qui n'existait pas lorsque j'ai écrit ce post pour la première fois. Voici une version mise à jour. (Et oui, cela a toujours été une façon très stupide de faire la conversion, mais c'était juste pour le plaisir!)

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
la source
3
  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
la source
ce code est bogué si la chaîne contient des caractères unicode. exemple:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp
3

Après avoir joué avec la solution de mangini pour la conversion de ArrayBuffervers String- ab2str(qui est la plus élégante et utile que j'ai trouvée - merci!), J'ai eu quelques problèmes lors de la manipulation de grands tableaux. Plus précisément, l'appel String.fromCharCode.apply(null, new Uint16Array(buf));génère une erreur:

arguments array passed to Function.prototype.apply is too large.

Afin de le résoudre (contournement), j'ai décidé de gérer l'entrée ArrayBufferen morceaux. La solution modifiée est donc:

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

La taille de bloc est définie sur 2^16car c'est la taille que j'ai trouvée pour fonctionner dans mon paysage de développement. La définition d'une valeur plus élevée a provoqué la même erreur. Il peut être modifié en définissant la CHUNK_SIZEvariable sur une valeur différente. Il est important d'avoir un nombre pair.

Remarque sur les performances - Je n'ai effectué aucun test de performances pour cette solution. Cependant, comme il est basé sur la solution précédente et peut gérer de grands tableaux, je ne vois aucune raison de ne pas l'utiliser.

yinon
la source
vous pouvez utiliser typedarray.subarray pour obtenir un morceau à la position et à la taille spécifiées, voici ce que je fais pour lire les en-têtes des formats binaires en js
Nikos M.
2

Pour node.js et également pour les navigateurs utilisant 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'))
}

Remarque: Les solutions ici n'ont pas fonctionné pour moi. Je dois prendre en charge node.js et les navigateurs et simplement sérialiser UInt8Array en une chaîne. Je pourrais le sérialiser en nombre [] mais cela occupe un espace inutile. Avec cette solution, je n'ai pas à me soucier des encodages car c'est en base64. Juste au cas où d'autres personnes auraient du mal avec le même problème ... Mes deux cents

cancerbero
la source
2

Disons que vous avez un arrayBuffer binaryStr:

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

puis vous affectez le texte à l'état.

Hilal Aissani
la source
1

La chaîne binaire "native" renvoyée par atob () est un tableau de 1 octet par caractère.

Nous ne devons donc pas stocker 2 octets dans un caractère.

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
la source
1

Oui:

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

Je ne recommanderais PAS d'utiliser des API obsolètes comme BlobBuilder

BlobBuilder est depuis longtemps déconseillé par l'objet Blob. Comparez le code dans la réponse de Dennis - où BlobBuilder est utilisé - avec le code ci-dessous:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

Notez à quel point c'est plus propre et moins gonflé par rapport à la méthode obsolète ... Oui, c'est certainement quelque chose à considérer ici.

realkstrawn93
la source
Je veux dire, oui, mais ce constructeur Blob n'était pas vraiment utilisable en 2012;)
gengkev
0

J'ai utilisé cela et travaille pour moi.

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
la source