435 votes

Conversion entre chaînes de caractères et ArrayBuffers

Existe-t-il une technique communément admise pour convertir efficacement des chaînes JavaScript en Tampons de tableau et vice-versa ? Plus précisément, j'aimerais pouvoir écrire le contenu d'un ArrayBuffer vers localStorage et de le relire.

1 votes

Je n'ai pas d'expérience dans ce domaine, mais à en juger par la documentation de l'API ( khronos.org/registry/typedarray/specs/latest ) si vous construisez un Int8Array ArrayBufferView il pourrait être possible d'utiliser simplement la notation entre crochets pour copier les caractères string[i] = buffer[i] et vice versa.

2 votes

@FK82, cela semble être une approche raisonnable (utilisation de Uint16Array s pour les caractères 16 bits de JS), mais les chaînes JavaScript sont immuables et vous ne pouvez pas assigner directement une position de caractère. Je devrais toujours copier String.fromCharCode(x) de chaque valeur dans le Uint16Array à une normale Array et ensuite appeler .join() sur le Array .

0 votes

@kpozin : Vrai, je n'ai pas vraiment pensé à ça.

195voto

mangini Points 1573

Bien que les solutions de Dennis et de gengkev consistant à utiliser Blob/FileReader fonctionnent, je ne suggérerais pas d'adopter cette approche. C'est 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;
}

EDIT :

El L'API de codage permet de résoudre la conversion des chaînes de caractères. problème. Consultez la réponse de Jeff Posnik sur Html5Rocks.com à l'article original ci-dessus.

Extrait :

L'API de codage simplifie la conversion entre les octets bruts et les chaînes JavaScript natives, quel que soit le codage 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>

0 votes

J'ai écrit une réponse détaillée sur votre article html5rocks (dès qu'il sera approuvé).

1 votes

Un tableau d'octets arbitraire survivra-t-il à un aller-retour avec cette méthode ? Y a-t-il des circonstances dans lesquelles nous pourrions perdre des données si une valeur du tableau (par ex. 0xD800 ) correspond à un point de code non valide ? L'objectif ultime ici est d'avoir un moyen fiable de faire persister le contenu de l'objet ArrayBuffer s.

0 votes

Kpozin : en théorie, cela devrait. Pour vous en assurer - au moins dans Chrome - vous pouvez exécuter ce code dans la console JavaScript et vérifier qu'il ne lève pas d'exception : for (var b=0x0000; b<=0xffff; b++) {abv[0]=b; if (new Uint16Array(str2ab(ab2str(ab)))[0]!==b) { throw "invalid conversion";} }

106voto

Ilmari Heikkinen Points 1151

Vous pouvez utiliser la bibliothèque stringencoding pour convertir des chaînes vers et depuis ArrayBuffers http://code.google.com/p/stringencoding/.

 var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
 

47voto

Ryan Weinstein Points 49

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

mais cela échoue si le tampon de tableau devient trop gros. La meilleure solution que j'ai trouvée consiste à utiliser String.fromCharCode(null,array); et à le scinder en opérations qui n'explosent pas la pile, mais sont plus rapides qu'un caractère à la fois.

La meilleure solution pour le tampon de grande matrice est:

 function arrayBufferToString(buffer,onSuccess,onFail)
{
    var bufView = new Uint16Array(buffer);
    var length = bufView.length;
    var result = '';
    for(var i = 0;i<length;i+=65535)
    {
        var addition = 65535;
        if(i + 65535 > length)
        {
            addition = length - i;
        }
        result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
    }
    if(result)
    {
        if(onSuccess)
        onSuccess(result);
    }else
    {
        if(onFail)
        onFail('buffer was invalid');
    }

    return result;

}
 

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

24voto

Dennis Points 1336

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

15voto

Dan Phillimore Points 390

(Mise à jour , Veuillez voir la 2e moitié de cette réponse, où j'ai (je l'espère) ont fourni une solution plus complète.)

J'ai aussi rencontré ce problème, les travaux suivants, pour moi, en FF 6 (dans un sens):

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 du texte ASCII représentations de les valeurs dans le tableau, plutôt que des caractères. - Il encore (devrait être) beaucoup plus efficace qu'une boucle. par exemple. Pour l'exemple ci-dessus, le résultat est 0004000000, au lieu de plusieurs caractères null & chr(4).

Edit:

Après recherche sur MDC ici, vous pouvez créer un ArrayBuffer d'un Array comme 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 de départ, ce qui vous permet de convertir ArrayBuffer <-> String comme 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 function pour la conversion d'un raw Unicode String d'un ArrayBuffer (ne fonctionne qu'avec ASCII/caractères d'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 ]);

Le ci-dessus vous permettent d'aller d' ArrayBuffer -> String & retour à ArrayBuffer , là où la chaîne peut être stockée dans eg. .localStorage :)

Espérons que cela aide,

Dan

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X