Как преобразовать массив uint8 в base64 Encoded String?
Я получил сообщение webSocket, я получаю строку в кодировке base64, преобразую ее в uint8 и работаю над ней, но теперь мне нужно отправить обратно, я получил массив uint8 и мне нужно преобразовать его в строку base64, чтобы я мог отправить ее. Как я могу сделать это преобразование?
19 ответов
npm install google-closure-library --save
//index.js, in its entirety
require("google-closure-library");
goog.require('goog.crypt.base64');
console.log(goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66), false));
$ node index.js
производит следующий вывод:
AVMbY2ZC
Вот документы: https://google.github.io/closure-library/api/goog.crypt.base64.html
Вот источник: https://github.com/google/closure-library/blob/master/closure/goog/crypt/base64.js#L124
Вот подпись:
/**
* Base64-encode an array of bytes.
*
* @param {Array<number>|Uint8Array} input An array of bytes (numbers with
* value in [0, 255]) to encode.
* @param {boolean=} opt_webSafe True indicates we should use the alternative
* alphabet, which does not require escaping for use in URLs.
* @return {string} The base64 encoded string.
*/
goog.crypt.base64.encodeByteArray = function(input, opt_webSafe)
Если ваши данные могут содержать многобайтовые последовательности (а не обычную последовательность ASCII), а в вашем браузере есть TextDecoder, вы должны использовать это для декодирования ваших данных (укажите необходимую кодировку для TextDecoder):
var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));
Если вам необходимо поддерживать браузеры, в которых нет TextDecoder (в настоящее время это только IE и Edge), то лучшим вариантом будет использование полизаполнения TextDecoder.
Если ваши данные содержат простой ASCII (не многобайтовый Unicode/UTF-8), то есть простая альтернатива, использующая String.fromCharCode
это должно быть достаточно универсально поддержано:
var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));
И декодировать строку base64 обратно в массив Uint8Array:
var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
return c.charCodeAt(0); }));
Если у вас очень большие буферы массивов, тогда может произойти сбой применения, и вам может понадобиться разделить буфер на части (основанный на том, который был опубликован @RohitSengar). Опять же, обратите внимание, что это верно только в том случае, если ваш буфер содержит только не многобайтовые символы 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));
Если вы используете Node.js, то вы можете использовать этот код для преобразования Uint8Array в base64
var b64 = Buffer.from(u8).toString('base64');
Чтобы закодировать base64
UInt8Array
с произвольными данными (не обязательно UTF-8) с использованием собственных функций браузера:
const base64_arraybuffer = async (data) => {
// Use a FileReader to generate a base64 data URI
const base64url = await new Promise((r) => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([data]))
})
/*
The result looks like
"data:application/octet-stream;base64,<your base64 data>",
so we split off the beginning:
*/
return base64url.split(",", 2)[1]
}
// example use:
await base64_arraybuffer(new UInt8Array([1,2,3,100,200]))
Все предложенные решения имеют серьезные проблемы. Некоторые решения не работают на больших массивах, некоторые выдают неправильный вывод, некоторые выдают ошибку при вызове btoa, если промежуточная строка содержит многобайтовые символы, другие потребляют больше памяти, чем необходимо.
Поэтому я реализовал функцию прямого преобразования, которая просто работает независимо от ввода. Он конвертирует около 5 миллионов байт в секунду на моей машине.
https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
Очень простое решение и тест на 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));
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);
}
Вы можете использовать эту функцию, если у вас очень большой массив Uint8Array. Это для Javascript, может быть полезно в случае FileReader readAsArrayBuffer.
Чистый JS - без строкового промежуточного этапа (без btoa)
В приведенном ниже решении я опускаю преобразование в строку. ИДЕЯ следующая:
- объедините 3 байта (3 элемента массива), и вы получите 24 бита
- разбить 24 бита на четыре 6-битных числа (которые принимают значения от 0 до 63)
- используйте эти числа в качестве индекса в алфавите base64
- угловой случай: при вводе байтового массива длина не делится на 3, затем добавьте
=
или==
в результате
Решение, приведенное ниже, работает с 3-байтовыми фрагментами, поэтому оно подходит для больших массивов. Аналогичное решение для преобразования base64 в двоичный массив (безatob
) ЗДЕСЬ
function bytesArrToBase64(arr) {
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // base64 alphabet
const bin = n => n.toString(2).padStart(8,0); // convert num to 8-bit binary string
const l = arr.length
let result = '';
for(let i=0; i<=(l-1)/3; i++) {
let c1 = i*3+1>=l; // case when "=" is on end
let c2 = i*3+2>=l; // case when "=" is on end
let chunk = bin(arr[3*i]) + bin(c1? 0:arr[3*i+1]) + bin(c2? 0:arr[3*i+2]);
let r = chunk.match(/.{1,6}/g).map((x,j)=> j==3&&c2 ? '=' :(j==2&&c1 ? '=':abc[+('0b'+x)]));
result += r.join('');
}
return result;
}
// ----------
// TEST
// ----------
let test = "Alice's Adventure in Wondeland.";
let testBytes = [...test].map(c=> c.charCodeAt(0) );
console.log('test string:', test);
console.log('bytes:', JSON.stringify(testBytes));
console.log('btoa ', btoa(test));
console.log('bytesArrToBase64', bytesArrToBase64(testBytes));
В браузере вы можете сделать:
Uint8Array --> Base64
btoa(String.fromCharCode.apply(null,new Uint8Array([1,2,3,255])))
Base64 --> Uint8Array
new Uint8Array([...atob('AQID/w==')].map(c=>c.charCodeAt(c)))
Документация MDN хорошо описывает btoa.
Поскольку у вас уже есть двоичные данные, вы можете преобразовать свой Uint8Array в строку ASCII и вызвать эту строку.
function encodeBase64Bytes(bytes: Uint8Array): string {
return btoa(
bytes.reduce((acc, current) => acc + String.fromCharCode(current), "")
);
}
Сложность с
btoa
возникает, когда вам нужно закодировать произвольные строки JS, которые могут занимать более одного байта, например
""
. Для обработки произвольных строк JS (которые являются UTF-16) вы должны сначала преобразовать строку в однобайтовое представление. Это неприменимо для этого варианта использования, поскольку у вас уже есть двоичные данные.
Связанная документация MDN описывает, как выглядит это преобразование для кодирования (и соответствующие шаги для декодирования).
Используйте следующее, чтобы преобразовать массив uint8 в строку в кодировке base64
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = [].slice.call(new Uint8Array(buffer));
bytes.forEach((b) => binary += String.fromCharCode(b));
return window.btoa(binary);
};
Сbtoa
работает только со строками, мы можем преобразовать Uint8Array в строки с помощьюString.fromCharCode
:
const toBase64 = uInt8Array => btoa(String.fromCharCode(...uInt8Array));
Я добавлю другое решение, которое работает с непечатаемыми диапазонами. Я думаю, это быстрее, чем цепочки TextEncoder
а также btoa
,
var blob = new Blob( [ uint8ArrayBuffer ], { type: "image/jpeg" } );
var imageUrl = URL.createObjectURL( blob );
Это использует API-интерфейсы HTML5 и поэтому, конечно, не будет работать на Node или других серверах на основе JS. Вы можете увидеть демо здесь.
Смотрите здесь https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
(Декодируйте строку Base64 в Uint8Array или ArrayBuffer с поддержкой Unicode)
Вот функция JS для этого:
Эта функция необходима, поскольку Chrome не принимает строку в кодировке base64 в качестве значения для applicationServerKey в 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;
}
Простое компактное решение
base64 в uint8массив
function base64ToUint8Array(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;
}
uint8массив в base64
const uint8ArrayToBase64 = async (data) => {
// Use a FileReader to generate a base64 data URI
const base64url = await new Promise((r) => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([data]))
})
/*
The result looks like
"data:application/octet-stream;base64,<your base64 data>",
so we split off the beginning:
*/
return base64url.split(",", 2)[1]
}
Пример:
base64ToUint8Array(await uint8ArrayToBase64(pdfBytes))
Очень хороший подход к этому показан на веб-сайте 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 "☸☹☺☻☼☾☿"
Если все, что вам нужно, это реализация JS-кода base64-кодера, чтобы вы могли отправить данные обратно, вы можете попробовать btoa
функция.
b64enc = btoa(uint);
Несколько быстрых заметок по btoa - это нестандартно, поэтому браузеры не обязаны его поддерживать. Тем не менее, большинство браузеров делают. Большие, по крайней мере. atob
это обратное преобразование.
Если вам нужна другая реализация, или вы обнаружите крайний случай, когда браузер не знает, о чем вы говорите, поиск кодера base64 для JS не будет слишком сложным.
Я думаю, что по какой-то причине их три на сайте моей компании...