ArrayBuffer do łańcucha zakodowanego w base64

203

Potrzebuję wydajnego (czytanego natywnego) sposobu na konwersję ciągu ArrayBufferna base64, który musi być użyty na postie wieloczęściowej.

zaheer
źródło

Odpowiedzi:

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

ale implementacje nienatywne są szybsze, np. https://gist.github.com/958841 patrz http://jsperf.com/encoding-xhr-image-data/6

mobz
źródło
10
Próbowałem implementacji innej niż natywna z linku, a konwersja bufora wielkości 1M zajęła 1 minutę i pół, podczas gdy powyższy kod pętli zajął tylko 1 sekundę.
cshu
1
Podoba mi się prostota tego podejścia, ale łączenie łańcuchów może być kosztowne. Wygląda na to, że budowanie tablicy znaków i umieszczanie join()ich na końcu jest znacznie szybsze w Firefox, IE i Safari (ale znacznie wolniej w Chrome): jsperf.com/tobase64-implementations
JLRishe
Używam tej funkcji do konwersji buforu tablicowego na base64, ale nie jestem w stanie odzyskać bufora tablicowego. Napisałem tutaj funkcję _base64ToArrayBuffer (): codeshare.io/PT4pb, ale to daje mi błąd jako:Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
bawejakunal
to nie działa ten JSZip. znajduję inny sposób github.com/michael/github/issues/137
RouR
1
Próbuję przesłać plik pdf 50 MB przy użyciu angualrjs i webapi2. Używam powyższego kodu linii, po przesłaniu pliku strona uległa awarii i została zawieszona. Zostałem użyty poniżej linii kodu, ale otrzymałem wartość zerową w metodzie webapi. „var base64String = btoa (String.fromCharCode.apply (null, nowy Uint8Array (arrayBuffer)));” proszę zasugerować jakikolwiek pomysł ...
prabhakaran S
102

To działa dobrze dla mnie:

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

W ES6 składnia jest nieco prostsza:

let base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));

Jak wskazano w komentarzach, ta metoda może powodować błąd czasu wykonywania w niektórych przeglądarkach, gdy ArrayBufferjest ona duża. Dokładny limit rozmiaru jest w każdym przypadku zależny od implementacji.

GOTO 0
źródło
38
Bardziej podoba mi się ta metoda dla zwięzłości, ale „błąd przekroczenia maksymalnego rozmiaru stosu wywołań”. Powyższa technika pętli omija ten problem.
Jay
13
Dostaję też błąd rozmiaru stosu, więc skorzystałem z odpowiedzi mobza i zadziałało świetnie.
David Jones
12
Nie działało w przypadku dużych buforów. Niewielka modyfikacja, aby działało:btoa([].reduce.call(new Uint8Array(bufferArray),function(p,c){return p+String.fromCharCode(c)},''))
laggingreflex
1
Próbuję przesłać plik pdf 50 MB przy użyciu angualrjs i webapi2. Używam powyższego kodu linii, po przesłaniu pliku strona uległa awarii i została zawieszona. Zostałem użyty poniżej linii kodu, ale otrzymałem wartość zerową w metodzie webapi. „var base64String = btoa (String.fromCharCode.apply (null, nowy Uint8Array (arrayBuffer)));” proszę zasugerować jakikolwiek pomysł ...
prabhakaran S
2
@Kugel btoajest bezpieczny dla znaków w zakresie kodu 0-255, ponieważ tak jest w tym przypadku (Pomyśl o 8 calach Uint8Array).
GOTO 0
42

Dla tych, którzy lubią to krótko, oto inne użycie, Array.reducektóre nie spowoduje przepełnienia stosu:

var base64 = btoa(
  new Uint8Array(arrayBuffer)
    .reduce((data, byte) => data + String.fromCharCode(byte), '')
);
gkunz
źródło
4
Nie jestem pewien, czy to naprawdę seksowne. W końcu tworzysz <amount of Bytes in the buffer>nowe ciągi.
Neonit
Jak o btoa(new Uint8Array(arraybuffer).reduce((data,byte)=>(data.push(String.fromCharCode(byte)),data),[]).join(''))?
Roy Tinker,
35

Istnieje inny asynchroniczny sposób użycia obiektów Blob i FileReader.

Nie testowałem wydajności. Ale to inny sposób myślenia.

function arrayBufferToBase64( buffer, callback ) {
    var blob = new Blob([buffer],{type:'application/octet-binary'});
    var reader = new FileReader();
    reader.onload = function(evt){
        var dataurl = evt.target.result;
        callback(dataurl.substr(dataurl.indexOf(',')+1));
    };
    reader.readAsDataURL(blob);
}

//example:
var buf = new Uint8Array([11,22,33]);
arrayBufferToBase64(buf, console.log.bind(console)); //"CxYh"
cuixiping
źródło
Użyj dataurl.split(',', 2)[1]zamiast dataurl.substr(dataurl.indexOf(',')+1).
Carter Medlin
Nie wydaje się, aby to działało. Według w3c.github.io/FileAPI/#issue-f80bda5b readAsDataURL teoretycznie może zwrócić procentowo zakodowane daneURI (i wygląda na to, że tak naprawdę jest w jsdom )
TS
@CarterMedlin Dlaczego byłby splitlepszy niż substring?
TS
podział jest krótszy. ale dataurl może zawierać jeden lub więcej przecinków (,), podział nie jest bezpieczny.
cuixiping
12

Użyłem tego i działa dla mnie.

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
źródło
Nie jest bezpieczne. Zobacz odpowiedź @chemoish
Kugel,
11

Zalecam, aby NIE używać natywnych btoastrategii - ponieważ nie kodują one poprawnie wszystkich ArrayBuffer

przepisz DOMs atob () i btoa ()

Ponieważ ciągi DOMS są ciągami zakodowanymi w 16 bitach, w większości przeglądarek wywołanie metody window.btoa w ciągu znaków Unicode spowoduje wyjątek Znak poza zakresem, jeśli znak przekroczy zakres 8-bitowego znaku zakodowanego w ASCII.

Chociaż nigdy nie spotkałem się z tym dokładnym błędem, odkryłem, że wiele z nich ArrayBuffer, które próbowałem zakodować, zostało zakodowanych nieprawidłowo.

Użyłbym rekomendacji MDN lub gist.

chemia
źródło
btoanie działa na String, ale OP pyta ArrayBuffer.
tsh
1
Bardzo dużo, tak wiele fragmentów, które zalecają niewłaściwą rzecz! Widziałem ten błąd wiele razy, gdy ludzie ślepo używają atob i btoa.
Kugel
4
Wszystkie bufory tablic powinny być dobrze zakodowane przy użyciu strategii w innych odpowiedziach, atob / btoa to tylko problem dla tekstu zawierającego znaki większe niż 0xFF (których tablice bajtowe z definicji nie mają). Ostrzeżenie MDN nie ma zastosowania, ponieważ podczas korzystania ze strategii w innych odpowiedziach masz zagwarantowany ciąg znaków, który składa się tylko ze znaków ASCII, ponieważ każda wartość z tablicy Uint8Array ma wartość od 0 do 255, co oznacza, że ​​gwarantowany jest String.fromCharCode aby zwrócić znak, który nie jest poza zasięgiem.
Jamesernator,
11
var blob = new Blob([arrayBuffer])

var reader = new FileReader();
reader.onload = function(event){
   var base64 =   event.target.result
};

reader.readAsDataURL(blob);
Alex
źródło
1
Dodaj wyjaśnienie do swojej odpowiedzi. Co oznacza ten kod?
Oscar
Podobny pomocny przykład MDN .
spenceryue
1
jest to zdecydowanie najszybsze podejście - dziesiątki razy szybsze niż inne w moich ograniczonych testach
jitin
chciałbym znaleźć to rozwiązanie jeszcze za 8 godzin. mój dzień nie zostałby zmarnowany; (dziękuję
Kaiser Shahid
7

Poniżej znajdują się 2 proste funkcje do konwersji Uint8Array na Base64 String iz powrotem

arrayToBase64String(a) {
    return btoa(String.fromCharCode(...a));
}

base64StringToArray(s) {
    let asciiString = atob(s);
    return new Uint8Array([...asciiString].map(char => char.charCodeAt(0)));
}
Steve Dixon
źródło
1
To myląca odpowiedź. To nie wygląda na prawidłowy JavaScript i czy Uint8Array jest ArrayBuffer?
user1153660,
@ user1153660 Dodaj functionsłowo kluczowe i powinno działać w nowoczesnej przeglądarce.
Yeti
Niesamowite! btoa (String.fromCharCode (... a)); jest najkrótszą wersją, jaką do tej pory widziałem, do kodowania Uint8Array.
Nicolo
1
Wygląda to dobrze, ale jeśli tablica jest zbyt duża, wygeneruje błąd przekroczenia maksymalnego rozmiaru stosu wywołań.
Paweł Psztyć
0

Możesz uzyskać normalną tablicę ArrayBufferza pomocą Array.prototype.slice. Użyj funkcji takiej jak Array.prototype.mapkonwersja bajtów na znaki i joinrazem na ciąg znaków.

function arrayBufferToBase64(ab){

    var dView = new Uint8Array(ab);   //Get a byte view        

    var arr = Array.prototype.slice.call(dView); //Create a normal array        

    var arr1 = arr.map(function(item){        
      return String.fromCharCode(item);    //Convert
    });

    return window.btoa(arr1.join(''));   //Form a string

}

Ta metoda jest szybsza, ponieważ nie ma w niej żadnych konkatenacji łańcuchów.

Charlie
źródło
Nie jest bezpieczne. Zobacz odpowiedź @chemoish
Kugel,
0

OP nie określił Środowiska Działającego, ale jeśli używasz Node.JS, istnieje bardzo prosty sposób na zrobienie czegoś takiego.

Zgodnie z oficjalnymi dokumentami Node.JS https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings

// This step is only necessary if you don't already have a Buffer Object
const buffer = Buffer.from(yourArrayBuffer);

const base64String = buffer.toString('base64');

Ponadto, jeśli pracujesz na przykład w Angular, klasa bufora zostanie również udostępniona w środowisku przeglądarki.

João Eduardo Soares e Silva
źródło
Twoja odpowiedź dotyczy tylko NodeJS i nie będzie działać w przeglądarce.
jvatic
1
@jvatic Widzę, OP nie określił wyraźnie środowiska uruchomieniowego, więc moja odpowiedź nie jest niepoprawna, tylko otagował Javascript. Zaktualizowałem więc odpowiedź, aby była bardziej zwięzła. Myślę, że to ważna odpowiedź, ponieważ szukałem, jak to zrobić i nie mogłem znaleźć najlepszej odpowiedzi na problem.
João Eduardo Soares e Silva
-3

Z mojej strony, korzystając z nawigatora Chrome, musiałem użyć DataView (), aby odczytać arrayBuffer

function _arrayBufferToBase64( tabU8A ) {
var binary = '';
let lecteur_de_donnees = new DataView(tabU8A);
var len = lecteur_de_donnees.byteLength;
var chaine = '';
var pos1;
for (var i = 0; i < len; i++) {
    binary += String.fromCharCode( lecteur_de_donnees.getUint8( i ) );
}
chaine = window.btoa( binary )
return chaine;}
Louvet Jean
źródło
-4
function _arrayBufferToBase64(uarr) {
    var strings = [], chunksize = 0xffff;
    var len = uarr.length;

    for (var i = 0; i * chunksize < len; i++){
        strings.push(String.fromCharCode.apply(null, uarr.subarray(i * chunksize, (i + 1) * chunksize)));
    }

    return strings.join("");
}

Jest to lepsze, jeśli używasz JSZip do rozpakowywania archiwum z ciągu

RouR
źródło