Bagaimana cara mengubah uint8 Array menjadi string yang dikodekan base64?

90

Saya mendapat komunikasi webSocket, saya menerima string yang dikodekan base64, mengubahnya menjadi uint8 dan mengerjakannya, tetapi sekarang saya perlu mengirim kembali, saya mendapatkan array uint8, dan perlu mengubahnya menjadi string base64, jadi saya dapat mengirimnya. Bagaimana saya dapat melakukan konversi ini?

Caio Keto
sumber
Pertanyaan "ArrayBuffer ke string yang dikodekan base64" berisi solusi yang lebih baik yang menangani semua karakter. stackoverflow.com/questions/9267899/…
Steve Hanov

Jawaban:

16

Semua solusi yang sudah diusulkan memiliki masalah yang parah. Beberapa solusi gagal bekerja pada array besar, beberapa memberikan output yang salah, beberapa memberikan kesalahan pada panggilan btoa jika string perantara berisi karakter multibyte, beberapa menggunakan lebih banyak memori daripada yang dibutuhkan.

Jadi saya menerapkan fungsi konversi langsung yang hanya berfungsi terlepas dari inputnya. Ini mengubah sekitar 5 juta byte per detik di mesin saya.

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

Egor Nepomnyaschih
sumber
Apakah memiliki base64abc sebagai array string lebih cepat dari sekadar menjadikannya string? "ABCDEFG..."?
Garr Godfrey
163

Jika data Anda mungkin berisi urutan multi-byte (bukan urutan ASCII biasa) dan browser Anda memiliki TextDecoder , Anda harus menggunakannya untuk mendekode data Anda (tentukan encoding yang diperlukan untuk TextDecoder):

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

Jika Anda perlu mendukung browser yang tidak memiliki TextDecoder (saat ini hanya IE dan Edge), maka opsi terbaik adalah menggunakan polyfill TextDecoder .

Jika data Anda berisi ASCII biasa (bukan multibyte Unicode / UTF-8) maka ada alternatif sederhana yang menggunakan String.fromCharCodeyang seharusnya cukup didukung secara universal:

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

Dan untuk mendekode string base64 kembali ke Uint8Array:

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

Jika Anda memiliki buffer array yang sangat besar maka penerapan mungkin gagal dan Anda mungkin perlu memotong buffer (berdasarkan yang diposting oleh @RohitSengar). Sekali lagi, perhatikan bahwa ini hanya benar jika buffer Anda hanya berisi karakter ASCII non-multibyte:

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
sumber
4
Ini berfungsi untuk saya di Firefox, tetapi Chrome tersedak dengan "RentangError Tidak Tertangkap: Ukuran tumpukan panggilan maksimum terlampaui" (melakukan btoa).
Michael Paulukonis
3
@MichaelPaulukonis Dugaan saya adalah bahwa sebenarnya String.fromCharCode.apply yang menyebabkan ukuran tumpukan terlampaui. Jika Anda memiliki Uint8Array yang sangat besar, Anda mungkin perlu membuat string secara berulang daripada menggunakan apply untuk melakukannya. Panggilan apply () meneruskan setiap elemen array Anda sebagai parameter ke fromCharCode, jadi jika panjang array 128.000 byte maka Anda akan mencoba membuat panggilan fungsi dengan 128.000 parameter yang kemungkinan besar akan meledakkan tumpukan.
kanaka
4
Terima kasih. Yang saya butuhkan hanyalahbtoa(String.fromCharCode.apply(null, myArray))
Glen Little
29
Ini tidak berfungsi jika array byte bukan Unicode yang valid.
Melab
11
Tidak ada karakter multibyte dalam string base64, atau dalam Uint8Array. TextDecoderbenar-benar hal yang salah untuk digunakan di sini, karena jika Anda Uint8Arraymemiliki byte dalam kisaran 128..255, dekoder teks akan secara keliru mengubahnya menjadi karakter unicode, yang akan merusak konverter base64.
riv
26

Solusi dan uji yang sangat sederhana untuk 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
sumber
4
Solusi terbersih!
realappie
Solusi sempurna
Haris ur Rehman
2
gagal pada data besar (seperti gambar) denganRangeError: Maximum call stack size exceeded
Maxim Khokhryakov
21

Jika Anda menggunakan Node.js maka Anda dapat menggunakan kode ini untuk mengubah Uint8Array menjadi base64

var b64 = Buffer.from(u8).toString('base64');
Fiach Reid
sumber
4
Ini adalah jawaban yang lebih baik daripada fungsi linting tangan di atas dalam hal kinerja.
Ben Liyanage
2
Hebat! Terima kasih. Jawaban terbaik yang pernah ada
Alan
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);
}

Anda dapat menggunakan fungsi ini jika Anda memiliki Uint8Array yang sangat besar. Ini untuk Javascript, dapat berguna dalam kasus FileReader readAsArrayBuffer.

Rohit Singh Sengar
sumber
2
Menariknya, di Chrome saya menghitung waktu ini pada buffer 300kb + dan menemukan melakukannya dalam potongan seperti Anda menjadi sedikit lebih lambat daripada melakukannya byte demi byte. Ini mengejutkan saya.
Matt
@Matt menarik. Ada kemungkinan bahwa untuk sementara waktu, Chrome sekarang telah mendeteksi konversi ini dan memiliki pengoptimalan khusus untuk itu dan memotong data dapat mengurangi efisiensinya.
kanaka
2
Ini tidak aman, bukan? Jika batas potongan saya memotong karakter yang dikodekan UTF8 multi-byte, maka fromCharCode () tidak akan dapat membuat karakter yang masuk akal dari byte di kedua sisi batas, bukan?
Jens
2
String.fromCharCode.apply()Metode @Jens tidak dapat mereproduksi UTF-8: Karakter UTF-8 dapat bervariasi panjangnya dari satu byte hingga empat byte, namun String.fromCharCode.apply()memeriksa UInt8Array dalam segmen UInt8, sehingga secara keliru mengasumsikan setiap karakter menjadi tepat satu byte dan independen dari tetangganya satu. Jika karakter yang dikodekan dalam input UInt8Array semuanya berada dalam rentang ASCII (single-byte), ini akan bekerja secara kebetulan, tetapi tidak dapat mereproduksi UTF-8 penuh. Anda membutuhkan TextDecoder atau algoritme serupa untuk itu.
Jamie Birch
1
@ Jens apa karakter multi-byte UTF8 yang dikodekan dalam array data biner? Kami tidak berurusan dengan string unicode di sini, tetapi dengan data biner arbitrer, yang TIDAK boleh diperlakukan sebagai titik kode utf-8.
riv
0

Berikut adalah Fungsi JS untuk ini:

Fungsi ini diperlukan karena Chrome tidak menerima string berenkode base64 sebagai nilai untuk applicationServerKey di pushManager. Belum berlangganan 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
sumber
3
Ini mengubah base64 menjadi Uint8Array. Tetapi pertanyaannya menanyakan bagaimana cara mengubah Uint8Array ke base64
Barry Michael Doyle
0

JS murni - tanpa string middlestep (tanpa btoa)

Dalam solusi di bawah ini saya menghilangkan konversi ke string. IDEA adalah sebagai berikut:

  • gabungkan 3 byte (3 elemen array) dan Anda mendapatkan 24-bit
  • membagi 24bits menjadi empat angka 6-bit (yang mengambil nilai dari 0 hingga 63)
  • gunakan angka itu sebagai indeks dalam alfabet base64
  • kasus sudut: ketika input array byte panjangnya tidak dibagi 3 kemudian menambah =atau ==menghasilkan

Solusi di bawah ini berfungsi pada potongan 3-byte sehingga bagus untuk array besar. Solusi serupa untuk mengonversi base64 ke array biner (tanpa atob) ada DI SINI

Kamil Kiełczewski
sumber
Saya suka kekompakan tetapi mengonversi ke string yang mewakili bilangan biner dan kemudian kembali jauh lebih lambat daripada solusi yang diterima.
Garr Godfrey
0

Gunakan perintah berikut untuk mengonversi larik uint8 menjadi string berenkode 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
sumber
-3

Jika yang Anda inginkan hanyalah implementasi JS dari encoder base64, sehingga Anda dapat mengirim data kembali, Anda dapat mencoba btoafungsinya.

b64enc = btoa(uint);

Beberapa catatan singkat tentang btoa - ini tidak standar, jadi browser tidak dipaksa untuk mendukungnya. Namun, kebanyakan browser melakukannya. Setidaknya yang besar. atobadalah kebalikan dari pertobatan.

Jika Anda memerlukan implementasi yang berbeda, atau Anda menemukan kasus tepi di mana browser tidak tahu apa yang Anda bicarakan, mencari encoder base64 untuk JS tidak akan terlalu sulit.

Saya pikir ada 3 dari mereka yang berkeliaran di situs web perusahaan saya, untuk beberapa alasan ...

Norguard
sumber
Terima kasih, saya tidak mencobanya sebelumnya.
Caio Keto
10
Beberapa catatan. btoa dan atob sebenarnya adalah bagian dari proses standardisasi HTML5 dan sebagian besar browser sudah mendukungnya dengan cara yang hampir sama. Kedua, btoa dan atob bekerja hanya dengan string. Menjalankan btoa di Uint8Array pertama-tama akan mengubah buffer menjadi string menggunakan toString (). Ini menghasilkan string "[object Uint8Array]". Mungkin bukan itu yang dimaksudkan.
kanaka
1
@CaioKeto Anda mungkin ingin mempertimbangkan untuk mengubah jawaban yang Anda pilih. Jawaban ini tidak benar.
kanaka
-4

npm instal 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.jsakan menulis AVMbY2Y = ke konsol.

mancini0
sumber
1
Lucu bahwa -vejawaban yang dipilih diterima daripada yang sangat tinggi +ve.
Vishnudev