Jak przekonwertować tablicę uint8 na ciąg zakodowany w base64?

Odpowiedzi:

15

Wszystkie proponowane rozwiązania mają poważne problemy. Niektóre rozwiązania nie działają na dużych tablicach, niektóre zapewniają błędne dane wyjściowe, inne generują błąd w wywołaniu btoa, jeśli łańcuch pośredni zawiera znaki wielobajtowe, a niektóre zajmują więcej pamięci niż potrzeba.

Zaimplementowałem więc funkcję konwersji bezpośredniej, która działa niezależnie od danych wejściowych. Konwertuje około 5 milionów bajtów na sekundę na moim komputerze.

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

Egor Nepomnyaschih
źródło
Czy posiadanie base64abc jako tablicy ciągów jest szybsze niż po prostu uczynienie z niej ciągu? "ABCDEFG..."?
Garr Godfrey
161

Jeśli Twoje dane mogą zawierać sekwencje wielobajtowe (a nie zwykłą sekwencję ASCII), a Twoja przeglądarka ma TextDecoder , powinieneś użyć tego do dekodowania danych (określ wymagane kodowanie dla TextDecoder):

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

Jeśli potrzebujesz obsługiwać przeglądarki, które nie mają TextDecodera (obecnie tylko IE i Edge), najlepszą opcją jest użycie wypełnienia TextDecoder .

Jeśli twoje dane zawierają zwykły ASCII (nie wielobajtowy Unicode / UTF-8), istnieje prosta alternatywa, String.fromCharCodektóra powinna być powszechnie obsługiwana:

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

Aby zdekodować ciąg base64 z powrotem do Uint8Array:

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

Jeśli masz bardzo duże bufory tablicowe, zastosowanie może się nie powieść i może być konieczne podzielenie buforu (na podstawie tego opublikowanego przez @RohitSengar). Zwróć uwagę, że jest to poprawne tylko wtedy, gdy twój bufor zawiera tylko inne niż wielobajtowe znaki ASCII:

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
źródło
4
To działa w moim przypadku w Firefoksie, ale Chrome dławi się z komunikatem „Uncaught RangeError: Maximum call stack przekroczony” (robi btoa).
Michael Paulukonis
3
@MichaelPaulukonis, przypuszczam, że to faktycznie String.fromCharCode.apply powoduje przekroczenie rozmiaru stosu. Jeśli masz bardzo dużą tablicę Uint8Array, prawdopodobnie będziesz musiał iteracyjnie zbudować łańcuch zamiast używać do tego zastosowania Apply. Wywołanie apply () przekazuje każdy element tablicy jako parametr do fromCharCode, więc jeśli tablica ma długość 128000 bajtów, wówczas próbowałbyś wykonać wywołanie funkcji z 128000 parametrami, co prawdopodobnie zniszczy stos.
kanaka
4
Dzięki. Wszystko, czego potrzebowałem, tobtoa(String.fromCharCode.apply(null, myArray))
Glen Little
29
To nie działa, jeśli tablica bajtów nie jest poprawnym kodem Unicode.
Melab
11
Nie ma znaków wielobajtowych w ciągu base64 lub w Uint8Array. TextDecoderjest tutaj absolutnie niewłaściwą rzeczą, ponieważ jeśli masz Uint8Arraybajty w zakresie 128..255, dekoder tekstu błędnie przekonwertuje je na znaki Unicode, co zepsuje konwerter base64.
riv
26

Bardzo proste rozwiązanie i test na 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));
impaktor
źródło
4
Najczystsze rozwiązanie!
realappie
Idealne rozwiązanie
Haris ur Rehman
2
zawodzi w przypadku dużych danych (takich jak obrazy)RangeError: 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);
}

Możesz użyć tej funkcji, jeśli masz bardzo duży Uint8Array. To jest dla Javascript, może być przydatne w przypadku FileReader readAsArrayBuffer.

Rohit Singh Sengar
źródło
2
Co ciekawe, w Chrome ustawiłem to w buforze 300kb + i stwierdziłem, że robienie tego w kawałkach, tak jak ty, jest o wiele wolniejsze niż robienie tego bajt po bajcie. To mnie zaskoczyło.
Matt
@Matt interesujące. Możliwe, że w międzyczasie Chrome wykrywa tę konwersję i ma dla niej określoną optymalizację, a dzielenie danych może zmniejszyć jej wydajność.
kanaka
2
To nie jest bezpieczne, prawda? Jeśli granica mojego fragmentu przecina wielobajtowy znak zakodowany w UTF8, to metoda fromCharCode () nie byłaby w stanie utworzyć sensownych znaków z bajtów po obu stronach granicy, prawda?
Jens
2
String.fromCharCode.apply()Metody @Jens nie mogą odtworzyć UTF-8: znaki UTF-8 mogą mieć różną długość od jednego bajtu do czterech bajtów, ale String.fromCharCode.apply()analizuje UInt8Array w segmentach UInt8, więc błędnie zakłada, że ​​każdy znak ma dokładnie jeden bajt długości i jest niezależny od sąsiedniego jedynki. Jeśli wszystkie znaki zakodowane w wejściowym UInt8Array znajdują się w zakresie ASCII (jednobajtowym), zadziała to przez przypadek, ale nie może odtworzyć pełnego UTF-8. Do tego potrzebny jest TextDecoder lub podobny algorytm .
Jamie Birch
1
@Jens jakie wielobajtowe zakodowane znaki UTF8 w binarnej tablicy danych? Nie mamy tutaj do czynienia z ciągami znaków Unicode, ale z dowolnymi danymi binarnymi, które NIE powinny być traktowane jako punkty kodowe utf-8.
RIV
15

Jeśli używasz Node.js, możesz użyć tego kodu, aby przekonwertować Uint8Array na base64

var b64 = Buffer.from(u8).toString('base64');
Fiach Reid
źródło
4
Jest to lepsza odpowiedź niż powyższe funkcje ręczne pod względem wydajności.
Ben Liyanage
2
Niesamowite! Dzięki. Najlepsza odpowiedź w historii
Alan
2
Idealny!! To będzie akceptowana odpowiedź!
m4l490n
1
To jest prawidłowa odpowiedź
Pablo Yabo
0

Oto funkcja JS do tego:

Ta funkcja jest potrzebna, ponieważ Chrome nie akceptuje ciągu znaków zakodowanych w base64 jako wartości dla applicationServerKey w 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
źródło
3
To konwertuje base64 na Uint8Array. Ale pytanie brzmi: jak przekonwertować Uint8Array na base64
Barry Michael Doyle
0

Czysty JS - bez łańcucha po środku (bez btoa)

W poniższym rozwiązaniu pomijam konwersję na string. IDEA jest następująca:

  • dołącz 3 bajty (3 elementy tablicy), a otrzymasz 24 bity
  • podziel 24 bity na cztery 6-bitowe liczby (które przyjmują wartości od 0 do 63)
  • użyj tych liczb jako indeksu w alfabecie base64
  • przypadek narożny: gdy wejściowa tablica bajtów nie jest podzielona przez 3, dodaj =lub ==do wyniku

Poniższe rozwiązanie działa na fragmentach 3-bajtowych, więc jest dobre dla dużych tablic. Podobne rozwiązanie do konwersji base64 na tablicę binarną (bez atob) jest TUTAJ

Kamil Kiełczewski
źródło
Podoba mi się zwartość, ale konwersja na ciągi reprezentujące liczbę binarną, a następnie z powrotem, jest znacznie wolniejsza niż przyjęte rozwiązanie.
Garr Godfrey
0

Użyj poniższego, aby przekonwertować tablicę uint8 na ciąg zakodowany algorytmem 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
źródło
-1

Bardzo dobre podejście do tego jest pokazane na stronie 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
źródło
-3

Jeśli wszystko, czego chcesz, to implementacja kodera base64 w JS, abyś mógł przesłać dane z powrotem, możesz wypróbować tę btoafunkcję.

b64enc = btoa(uint);

Kilka krótkich uwag na temat btoa - jest niestandardowy, więc przeglądarki nie są zmuszane do jego obsługi. Jednak większość przeglądarek to robi. Przynajmniej te duże. atobjest odwrotną konwersją.

Jeśli potrzebujesz innej implementacji lub znajdziesz przypadek skrajny, w którym przeglądarka nie ma pojęcia, o czym mówisz, wyszukiwanie kodera base64 dla JS nie byłoby zbyt trudne.

Myślę, że z jakiegoś powodu na stronie mojej firmy kręcą się 3 z nich ...

Norguard
źródło
Dzięki, wcześniej tego nie próbowałem.
Caio Keto
10
Kilka uwag. btoa i atob są w rzeczywistości częścią procesu standaryzacji HTML5 i większość przeglądarek obsługuje je już w większości w ten sam sposób. Po drugie, btoa i atob działają tylko ze stringami. Uruchomienie btoa na Uint8Array najpierw skonwertuje bufor na ciąg przy użyciu toString (). Powoduje to powstanie ciągu „[obiekt Uint8Array]”. Prawdopodobnie nie jest to zamierzone.
kanaka
1
@CaioKeto możesz rozważyć zmianę wybranej odpowiedzi. Ta odpowiedź jest nieprawidłowa.
kanaka
-4

npm zainstaluj 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.jszapisze AVMbY2Y = na konsoli.

mancini0
źródło
1
To zabawne, że -vegłosowana odpowiedź jest akceptowana, a nie wysoce +ve.
Vishnudev