Wie konvertiere ich uint8 Array in base64 Encoded String?

89

Ich habe eine webSocket-Kommunikation, ich erhalte eine Base64-codierte Zeichenfolge, konvertiere sie in uint8 und arbeite daran, aber jetzt muss ich zurücksenden, ich habe das uint8-Array und muss es in eine base64-Zeichenfolge konvertieren, damit ich es senden kann. Wie kann ich diese Konvertierung vornehmen?

Caio Keto
quelle

Antworten:

15

Alle bereits vorgeschlagenen Lösungen weisen schwerwiegende Probleme auf. Einige Lösungen funktionieren nicht mit großen Arrays, andere liefern eine falsche Ausgabe, andere geben beim Btoa-Aufruf einen Fehler aus, wenn eine Zwischenzeichenfolge Multibyte-Zeichen enthält, andere verbrauchen mehr Speicher als benötigt.

Also habe ich eine direkte Konvertierungsfunktion implementiert, die unabhängig von der Eingabe funktioniert. Es konvertiert ungefähr 5 Millionen Bytes pro Sekunde auf meinem Computer.

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

Egor Nepomnyaschih
quelle
Ist base64abc als Array von Strings schneller als nur ein String? "ABCDEFG..."?
Garr Godfrey
161

Wenn Ihre Daten möglicherweise Mehrbyte -Sequenzen enthalten (keine einfache ASCII-Sequenz) und Ihr Browser über TextDecoder verfügt , sollten Sie diese zum Dekodieren Ihrer Daten verwenden (geben Sie die erforderliche Codierung für den TextDecoder an):

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

Wenn Sie Browser unterstützen müssen, die keinen TextDecoder haben (derzeit nur IE und Edge), ist die beste Option die Verwendung einer TextDecoder-Polyfüllung .

Wenn Ihre Daten einfaches ASCII enthalten (kein Multibyte-Unicode / UTF-8), gibt es eine einfache Alternative String.fromCharCode, die allgemein unterstützt werden sollte:

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

Und um die base64-Zeichenfolge wieder in ein Uint8Array zu dekodieren:

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

Wenn Sie über sehr große Array-Puffer verfügen, schlägt die Anwendung möglicherweise fehl und Sie müssen den Puffer möglicherweise aufteilen (basierend auf dem von @RohitSengar veröffentlichten Puffer). Beachten Sie erneut, dass dies nur korrekt ist, wenn Ihr Puffer nur Nicht-Multibyte-ASCII-Zeichen enthält:

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
quelle
4
Dies funktioniert für mich in Firefox, aber Chrome drosselt mit "Uncaught RangeError: Maximale Aufrufstapelgröße überschritten" (Btoa ausführen).
Michael Paulukonis
3
@MichaelPaulukonis meine Vermutung ist, dass es tatsächlich die Zeichenfolge String.fromCharCode.apply ist, die dazu führt, dass die Stapelgröße überschritten wird. Wenn Sie ein sehr großes Uint8Array haben, müssen Sie die Zeichenfolge wahrscheinlich iterativ aufbauen, anstatt die Anwendung zu verwenden. Der Apply () -Aufruf übergibt jedes Element Ihres Arrays als Parameter an fromCharCode. Wenn das Array also 128000 Byte lang ist, würden Sie versuchen, einen Funktionsaufruf mit 128000 Parametern durchzuführen, der wahrscheinlich den Stapel sprengt.
kanaka
4
Vielen Dank. Alles was ich brauchte warbtoa(String.fromCharCode.apply(null, myArray))
Glen Little
29
Dies funktioniert nicht, wenn das Byte-Array kein gültiger Unicode ist.
Melab
11
Es gibt keine Multibyte-Zeichen in einer Base64-Zeichenfolge oder in Uint8Array. TextDecoderist hier absolut falsch, denn wenn Ihr Uint8ArrayByte im Bereich 128..255 liegt, konvertiert der Textdecoder diese fälschlicherweise in Unicode-Zeichen, wodurch der base64-Konverter beschädigt wird.
Riv
26

Sehr einfache Lösung und Test für 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
quelle
4
Sauberste Lösung!
Realappie
Perfekte Lösung
Haris ur Rehman
2
es schlägt bei großen Datenmengen (wie Bildern) mitRangeError: 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);
}

Sie können diese Funktion verwenden, wenn Sie ein sehr großes Uint8Array haben. Dies ist für Javascript, kann im Fall von FileReader readAsArrayBuffer nützlich sein.

Rohit Singh Sengar
quelle
2
Interessanterweise habe ich dies in Chrome auf einem Puffer von über 300 KB zeitlich festgelegt und festgestellt, dass es in Blöcken, wie Sie es tun, etwas langsamer ist als Byte für Byte. Das hat mich überrascht.
Matt
@ Matt interessant. Es ist möglich, dass Chrome diese Konvertierung inzwischen erkannt und eine spezifische Optimierung vorgenommen hat. Durch das Aufteilen der Daten kann die Effizienz beeinträchtigt werden.
kanaka
2
Das ist nicht sicher, oder? Wenn die Grenze meines Chunks ein Multi-Byte-UTF8-codiertes Zeichen durchschneidet, kann fromCharCode () aus den Bytes auf beiden Seiten der Grenze keine sinnvollen Zeichen erstellen, oder?
Jens
2
@ Jens- String.fromCharCode.apply()Methoden können UTF-8 nicht reproduzieren: UTF-8-Zeichen können in der Länge von einem Byte bis zu vier Bytes variieren, String.fromCharCode.apply()untersuchen jedoch ein UInt8Array in Segmenten von UInt8, sodass fälschlicherweise angenommen wird, dass jedes Zeichen genau ein Byte lang und unabhängig vom Nachbarn ist Einsen. Wenn sich die in der Eingabe UInt8Array codierten Zeichen alle zufällig im ASCII-Bereich (Einzelbyte) befinden, funktioniert dies zufällig, kann jedoch nicht die vollständige UTF-8 reproduzieren. Dazu benötigen Sie TextDecoder oder einen ähnlichen Algorithmus .
Jamie Birch
1
@Jens welche Multi-Byte-UTF8-codierten Zeichen in einem binären Datenarray? Wir haben es hier nicht mit Unicode-Strings zu tun, sondern mit beliebigen Binärdaten, die NICHT als utf-8-Codepunkte behandelt werden sollten.
Riv
15

Wenn Sie Node.js verwenden, können Sie diesen Code verwenden, um Uint8Array in base64 zu konvertieren

var b64 = Buffer.from(u8).toString('base64');
Fiach Reid
quelle
4
Dies ist eine bessere Antwort als die oben genannten handgerollten Funktionen in Bezug auf die Leistung.
Ben Liyanage
2
Genial! Vielen Dank. Beste Antwort aller Zeiten
Alan
2
Perfekt!! Dies soll die akzeptierte Antwort sein!
m4l490n
1
Dies ist die richtige Antwort
Pablo Yabo
0

Hier ist eine JS-Funktion dazu:

Diese Funktion wird benötigt, da Chrome noch keine Base64-codierte Zeichenfolge als Wert für applicationServerKey in pushManager.subscribe akzeptiert. 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
quelle
3
Dadurch wird base64 in Uint8Array konvertiert. Aber die Frage fragt, wie man Uint8Array in base64 konvertiert
Barry Michael Doyle
0

Pure JS - kein String-Mittelschritt (kein Btoa)

In der folgenden Lösung lasse ich die Konvertierung in einen String aus. IDEE ist folgendes:

  • Verbinden Sie 3 Bytes (3 Array-Elemente) und Sie erhalten 24-Bit
  • Teilen Sie 24 Bit in vier 6-Bit-Zahlen auf (die Werte von 0 bis 63 annehmen).
  • Verwenden Sie diese Zahlen als Index im base64-Alphabet
  • Eckfall: Wenn das Eingabe-Byte-Array nicht durch 3 geteilt wird, addieren =oder ==ergeben

Die folgende Lösung funktioniert für 3-Byte-Blöcke, sodass sie für große Arrays geeignet ist. Eine ähnliche Lösung zum Konvertieren von base64 in ein binäres Array (ohne atob) finden Sie HIER

Kamil Kiełczewski
quelle
Ich mag die Kompaktheit, aber die Konvertierung in Zeichenfolgen, die Binärzahlen und dann zurück darstellen, ist viel langsamer als die akzeptierte Lösung.
Garr Godfrey
0

Verwenden Sie Folgendes, um das uint8-Array in eine Base64-codierte Zeichenfolge zu konvertieren

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
quelle
-1

Ein sehr guter Ansatz hierzu wird auf der Website des Mozilla Developer Network gezeigt :

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
quelle
-3

Wenn Sie nur eine JS-Implementierung eines Base64-Encoders benötigen, damit Sie Daten zurücksenden können, können Sie die btoaFunktion ausprobieren .

b64enc = btoa(uint);

Ein paar kurze Hinweise zu btoa - es ist nicht Standard, sodass Browser nicht gezwungen sind, es zu unterstützen. Die meisten Browser tun dies jedoch. Zumindest die Großen. atobist die entgegengesetzte Umwandlung.

Wenn Sie eine andere Implementierung benötigen oder einen Randfall finden, in dem der Browser keine Ahnung hat, wovon Sie sprechen, wäre die Suche nach einem Base64-Encoder für JS nicht allzu schwierig.

Ich denke, aus irgendeinem Grund hängen 3 davon auf der Website meines Unternehmens herum ...

Norguard
quelle
Danke, das habe ich vorher nicht ausprobiert.
Caio Keto
10
Ein paar Notizen. btoa und atob sind tatsächlich Teil des HTML5-Standardisierungsprozesses, und die meisten Browser unterstützen sie bereits größtenteils auf die gleiche Weise. Zweitens arbeiten btoa und atob nur mit Strings. Wenn Sie btoa auf dem Uint8Array ausführen, wird der Puffer zuerst mit toString () in einen String konvertiert. Dies führt zu der Zeichenfolge "[Objekt Uint8Array]". Das ist wahrscheinlich nicht beabsichtigt.
Kanaka
1
@CaioKeto Vielleicht möchten Sie Ihre ausgewählte Antwort ändern. Diese Antwort ist nicht korrekt.
Kanaka
-4

npm installiere Google-Closure-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.jswürde AVMbY2Y = in die Konsole schreiben .

mancini0
quelle
1
Es ist lustig, dass eine -veabgestimmte Antwort eher akzeptiert wird als eine hohe +ve.
Vishnudev