Comment convertir un tableau uint8 en chaîne encodée en base64?

89

J'ai une communication webSocket, je reçois une chaîne encodée en base64, je la convertis en uint8 et je travaille dessus, mais maintenant je dois renvoyer, j'ai le tableau uint8 et je dois le convertir en chaîne base64, afin que je puisse l'envoyer. Comment puis-je effectuer cette conversion?

Caio Keto
la source

Réponses:

15

Toutes les solutions déjà proposées présentent de graves problèmes. Certaines solutions ne fonctionnent pas sur de grands tableaux, certaines fournissent une sortie erronée, certaines lancent une erreur sur un appel btoa si une chaîne intermédiaire contient des caractères multi-octets, certaines consomment plus de mémoire que nécessaire.

J'ai donc implémenté une fonction de conversion directe qui fonctionne juste indépendamment de l'entrée. Il convertit environ 5 millions d'octets par seconde sur ma machine.

https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727

Egor Nepomnyaschih
la source
Avoir base64abc comme un tableau de chaînes est-il plus rapide que d'en faire une chaîne? "ABCDEFG..."?
Garr Godfrey
161

Si vos données peuvent contenir des séquences multi-octets (pas une séquence ASCII simple) et que votre navigateur a TextDecoder , vous devez l'utiliser pour décoder vos données (spécifiez l'encodage requis pour TextDecoder):

var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));

Si vous devez prendre en charge les navigateurs qui n'ont pas TextDecoder (actuellement juste IE et Edge), la meilleure option est d'utiliser un polyfill TextDecoder .

Si vos données contiennent de l'ASCII brut (pas Unicode / UTF-8 multi-octets), il existe une alternative simple d'utilisation String.fromCharCodequi devrait être prise en charge de manière assez universelle:

var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));

Et pour décoder la chaîne base64 en un Uint8Array:

var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
    return c.charCodeAt(0); }));

Si vous avez des tampons de tableau très volumineux, l'application peut échouer et vous devrez peut-être segmenter le tampon (en fonction de celui publié par @RohitSengar). Encore une fois, notez que cela n'est correct que si votre tampon contient uniquement des caractères ASCII non multi-octets:

function Uint8ToString(u8a){
  var CHUNK_SZ = 0x8000;
  var c = [];
  for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
    c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
  }
  return c.join("");
}
// Usage
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(Uint8ToString(u8));
Kanaka
la source
4
Cela fonctionne pour moi dans Firefox, mais Chrome s'étouffe avec "Uncaught RangeError: Taille maximale de la pile d'appels dépassée" (faisant le btoa).
Michael Paulukonis
3
@MichaelPaulukonis je suppose que c'est en fait le String.fromCharCode.apply qui provoque le dépassement de la taille de la pile. Si vous avez un très grand Uint8Array, vous devrez probablement construire itérativement la chaîne au lieu d'utiliser le apply pour le faire. L'appel apply () passe chaque élément de votre tableau en tant que paramètre à fromCharCode, donc si le tableau a une longueur de 128 000 octets, vous essayez de faire un appel de fonction avec 128 000 paramètres qui risque de faire exploser la pile.
kanaka
4
Merci. Tout ce dont j'avais besoin étaitbtoa(String.fromCharCode.apply(null, myArray))
Glen Little
29
Cela ne fonctionne pas si le tableau d'octets n'est pas Unicode valide.
Melab
11
Il n'y a pas de caractères multi-octets dans une chaîne base64 ou dans Uint8Array. TextDecoderest absolument la mauvaise chose à utiliser ici, car si votre Uint8Arraya octets dans la plage 128..255, le décodeur de texte les convertira par erreur en caractères unicode, ce qui cassera le convertisseur base64.
riv
26

Solution très simple et test pour JavaScript!

ToBase64 = function (u8) {
    return btoa(String.fromCharCode.apply(null, u8));
}

FromBase64 = function (str) {
    return atob(str).split('').map(function (c) { return c.charCodeAt(0); });
}

var u8 = new Uint8Array(256);
for (var i = 0; i < 256; i++)
    u8[i] = i;

var b64 = ToBase64(u8);
console.debug(b64);
console.debug(FromBase64(b64));
impactro
la source
4
La solution la plus propre!
realappie
Solution parfaite
Haris ur Rehman
2
il échoue sur des données volumineuses (telles que des images) avecRangeError: Maximum call stack size exceeded
Maxim Khokhryakov
18
function Uint8ToBase64(u8Arr){
  var CHUNK_SIZE = 0x8000; //arbitrary number
  var index = 0;
  var length = u8Arr.length;
  var result = '';
  var slice;
  while (index < length) {
    slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length)); 
    result += String.fromCharCode.apply(null, slice);
    index += CHUNK_SIZE;
  }
  return btoa(result);
}

Vous pouvez utiliser cette fonction si vous avez un très grand Uint8Array. Ceci est pour Javascript, peut être utile dans le cas de FileReader readAsArrayBuffer.

Rohit Singh Sengar
la source
2
Fait intéressant, dans Chrome, j'ai chronométré cela sur un tampon de 300 Ko + et j'ai trouvé que le faire par morceaux comme si vous deviez être un peu plus lent que de le faire octet par octet. Cela m'a surpris.
Matt
@Matt intéressant. Il est possible qu'entre-temps, Chrome ait maintenant détecté cette conversion et dispose d'une optimisation spécifique pour celle-ci et la segmentation des données peut réduire son efficacité.
kanaka
2
Ce n'est pas sûr, n'est-ce pas? Si la limite de mon morceau traverse un caractère codé UTF8 multi-octets, alors fromCharCode () ne serait pas en mesure de créer des caractères sensibles à partir des octets des deux côtés de la limite, n'est-ce pas?
Jens
2
Les String.fromCharCode.apply()méthodes @Jens ne peuvent pas reproduire UTF-8: les caractères UTF-8 peuvent varier en longueur d'un octet à quatre octets, mais String.fromCharCode.apply()examine un UInt8Array dans des segments de UInt8, donc il suppose à tort que chaque caractère est exactement un octet de long et indépendant du voisin ceux. Si les caractères encodés dans l'entrée UInt8Array se trouvent tous dans la plage ASCII (à un octet), cela fonctionnera par hasard, mais il ne pourra pas reproduire l'UTF-8 complet. Vous avez besoin de TextDecoder ou d'un algorithme similaire pour cela.
Jamie Birch
1
@Jens Quels caractères codés UTF8 multi-octets dans un tableau de données binaires? Nous ne traitons pas ici de chaînes Unicode, mais de données binaires arbitraires, qui ne doivent PAS être traitées comme des points de code utf-8.
riv
15

Si vous utilisez Node.js, vous pouvez utiliser ce code pour convertir Uint8Array en base64

var b64 = Buffer.from(u8).toString('base64');
Fiach Reid
la source
4
C'est une meilleure réponse que les fonctions roulées à la main ci-dessus en termes de performances.
Ben Liyanage le
2
Impressionnant! Merci. Meilleure réponse de tous les temps
Alan
2
Parfait!! Ce sera la réponse acceptée!
m4l490n le
1
C'est la bonne réponse
Pablo Yabo
0

Voici une fonction JS à ceci:

Cette fonction est nécessaire car Chrome n'accepte pas encore une chaîne encodée en base64 comme valeur pour applicationServerKey dans pushManager.subscribe https://bugs.chromium.org/p/chromium/issues/detail?id=802280

function urlBase64ToUint8Array(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/');

  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);

  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}
lucss
la source
3
Cela convertit base64 en Uint8Array. Mais la question demande comment convertir Uint8Array en base64
Barry Michael Doyle
0

Pure JS - pas de chaîne intermédiaire (pas de btoa)

Dans la solution ci-dessous, j'omets la conversion en chaîne. IDEA suit:

  • joindre 3 octets (3 éléments de tableau) et vous obtenez 24 bits
  • diviser 24 bits en quatre nombres de 6 bits (qui prennent des valeurs de 0 à 63)
  • utiliser ces nombres comme index dans l'alphabet base64
  • cas d'angle: lorsque le tableau d'octets d'entrée, la longueur n'est pas divisée par 3 puis ajouter =ou ==au résultat

La solution ci-dessous fonctionne sur des blocs de 3 octets, elle convient donc aux grands tableaux. Une solution similaire pour convertir base64 en tableau binaire (sans atob) est ICI

Kamil Kiełczewski
la source
J'aime la compacité mais la conversion en chaînes représentant un nombre binaire puis en arrière est beaucoup plus lente que la solution acceptée.
Garr Godfrey
0

Utilisez ce qui suit pour convertir le tableau uint8 en chaîne encodée en base64

function arrayBufferToBase64(buffer) {
            var binary = '';
            var bytes = [].slice.call(new Uint8Array(buffer));
            bytes.forEach((b) => binary += String.fromCharCode(b));
            return window.btoa(binary);
        };
KARTHIKEYAN.A
la source
-1

Une très bonne approche à ce sujet est présentée sur le site Web de Mozilla Developer Network :

function btoaUTF16 (sString) {
    var aUTF16CodeUnits = new Uint16Array(sString.length);
    Array.prototype.forEach.call(aUTF16CodeUnits, function (el, idx, arr) { arr[idx] = sString.charCodeAt(idx); });
    return btoa(String.fromCharCode.apply(null, new Uint8Array(aUTF16CodeUnits.buffer)));
}

function atobUTF16 (sBase64) {
    var sBinaryString = atob(sBase64), aBinaryView = new Uint8Array(sBinaryString.length);
    Array.prototype.forEach.call(aBinaryView, function (el, idx, arr) { arr[idx] = sBinaryString.charCodeAt(idx); });
    return String.fromCharCode.apply(null, new Uint16Array(aBinaryView.buffer));
}

var myString = "☸☹☺☻☼☾☿";

var sUTF16Base64 = btoaUTF16(myString);
console.log(sUTF16Base64);    // Shows "OCY5JjomOyY8Jj4mPyY="

var sDecodedString = atobUTF16(sUTF16Base64);
console.log(sDecodedString);  // Shows "☸☹☺☻☼☾☿"

Rosberg Linhares
la source
-3

Si tout ce que vous voulez est une implémentation JS d'un encodeur base64, afin que vous puissiez renvoyer des données, vous pouvez essayer la btoafonction.

b64enc = btoa(uint);

Quelques notes rapides sur le btoa - ce n'est pas standard, donc les navigateurs ne sont pas obligés de le prendre en charge. Cependant, la plupart des navigateurs le font. Les gros, au moins. atobest la conversion opposée.

Si vous avez besoin d'une implémentation différente, ou si vous trouvez un cas de pointe où le navigateur n'a aucune idée de ce dont vous parlez, la recherche d'un encodeur base64 pour JS ne serait pas trop difficile.

Je pense qu'il y en a 3 qui traînent sur le site Web de mon entreprise, pour une raison quelconque ...

Norguard
la source
Merci, je n'ai pas essayé ça avant.
Caio Keto
10
Quelques notes. btoa et atob font en fait partie du processus de normalisation HTML5 et la plupart des navigateurs les prennent déjà en charge de la même manière. Deuxièmement, btoa et atob fonctionnent uniquement avec des chaînes. L'exécution de btoa sur Uint8Array convertira d'abord le tampon en une chaîne en utilisant toString (). Il en résulte la chaîne "[object Uint8Array]". Ce n'est probablement pas ce qui est prévu.
kanaka
1
@CaioKeto, vous voudrez peut-être envisager de modifier la réponse sélectionnée. Cette réponse n'est pas correcte.
kanaka
-4

npm installer google-closing-library --save

require("google-closure-library");
goog.require('goog.crypt.base64');

var result =goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66));
console.log(result);

$node index.jsécrirait AVMbY2Y = sur la console.

mancini0
la source
1
C'est drôle qu'une -veréponse votée soit acceptée plutôt qu'une réponse hautement +ve.
Vishnudev