Как преобразовать массив uint8 в закодированную строку base64?


89

У меня есть связь через webSocket, я получаю строку в кодировке base64, конвертирую ее в uint8 и работаю над ней, но теперь мне нужно отправить обратно, я получил массив uint8 и мне нужно преобразовать его в строку base64, чтобы я мог его отправить. Как я могу сделать это преобразование?


Ответы:


15

Все уже предложенные решения имеют серьезные проблемы. Некоторые решения не работают с большими массивами, некоторые дают неправильный вывод, некоторые выдают ошибку при вызове btoa, если промежуточная строка содержит многобайтовые символы, некоторые потребляют больше памяти, чем необходимо.

Поэтому я реализовал функцию прямого преобразования, которая работает независимо от ввода. На моей машине он преобразует около 5 миллионов байт в секунду.

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


Является ли base64abc массивом строк быстрее, чем просто преобразовать его в строку? "ABCDEFG..."?
Гарр Годфри,

161

Если ваши данные могут содержать многобайтовые последовательности (а не простую последовательность 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));

4
Это работает для меня в Firefox, но Chrome задыхается с "Uncaught RangeError: Максимальный размер стека вызовов превышен" (выполняя btoa).
Майкл Паулюконис

3
@MichaelPaulukonis, я предполагаю, что на самом деле String.fromCharCode.apply вызывает превышение размера стека. Если у вас очень большой массив Uint8Array, вам, вероятно, потребуется итеративно создавать строку вместо использования для этого apply. Вызов apply () передает каждый элемент вашего массива в качестве параметра в fromCharCode, поэтому, если массив имеет длину 128000 байт, вы попытаетесь выполнить вызов функции с 128000 параметрами, что, вероятно, взорвет стек.
kanaka

4
Спасибо. Все, что мне было нужно, этоbtoa(String.fromCharCode.apply(null, myArray))
Глен Литтл

29
Это не работает, если массив байтов не является допустимым Unicode.
Melab

11
Нет многобайтовых символов в строке base64 или в Uint8Array. TextDecoderабсолютно неправильная вещь для использования здесь, потому что, если у вас Uint8Arrayесть байты в диапазоне 128..255, текстовый декодер ошибочно преобразует их в символы Unicode, что нарушит преобразователь base64.
riv

26

Очень простое решение и тест на 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));

4
Самое чистое решение!
realappie

Идеальное решение
Харис ур Рехман

2
он не работает на больших данных (таких как изображения) сRangeError: Maximum call stack size exceeded
Максим Хохряков

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

Вы можете использовать эту функцию, если у вас очень большой Uint8Array. Это для Javascript, может быть полезно в случае FileReader readAsArrayBuffer.


2
Интересно, что в Chrome я рассчитал это на буфер размером 300 Кбайт + и обнаружил, что делаю это по частям, как будто вы должны быть немного медленнее, чем побайтно. Это меня удивило.
Мэтт

@ Матт интересно. Вполне возможно, что тем временем Chrome обнаруживает это преобразование и имеет для него специальную оптимизацию, а разбивка данных может снизить его эффективность.
канак

2
Это ведь небезопасно? Если граница моего фрагмента прорезает многобайтовый символ в кодировке UTF8, тогда fromCharCode () не сможет создавать разумные символы из байтов по обе стороны границы, не так ли?
Йенс

2
String.fromCharCode.apply()Методы @Jens не могут воспроизводить UTF-8: символы UTF-8 могут иметь различную длину от одного байта до четырех байтов, но String.fromCharCode.apply()проверяет UInt8Array в сегментах UInt8, поэтому он ошибочно предполагает, что каждый символ имеет длину ровно один байт и не зависит от соседних ед. Если все символы, закодированные во входном UInt8Array, находятся в диапазоне ASCII (однобайтовый), он сработает случайно, но не сможет воспроизвести полный UTF-8. Для этого вам понадобится TextDecoder или аналогичный алгоритм .
Джейми Берч

1
@Jens, какие многобайтовые символы в кодировке UTF8 в массиве двоичных данных? Здесь мы имеем дело не со строками Unicode, а с произвольными двоичными данными, которые НЕ должны рассматриваться как кодовые точки utf-8.
riv

15

Если вы используете Node.js, вы можете использовать этот код для преобразования Uint8Array в base64.

var b64 = Buffer.from(u8).toString('base64');

4
Это лучший ответ, чем функции, скрученные вручную выше, с точки зрения производительности.
Бен Лиянаге,

2
Потрясающие! Спасибо. Лучший ответ на свете
Алан

2
Отлично!! Это будет принятый ответ!
m4l490n

1
Это правильный ответ
Пабло Ябо

0

Вот функция 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;
}

3
Это преобразует base64 в Uint8Array. Но вопрос в том, как преобразовать Uint8Array в base64
Барри Майкл Дойл

0

Чистый JS - без строкового промежуточного этапа (без btoa)

В приведенном ниже решении я опускаю преобразование в строку. ИДЕЯ следующая:

  • объедините 3 байта (3 элемента массива), и вы получите 24 бита
  • разделить 24 бита на четыре 6-битных числа (которые принимают значения от 0 до 63)
  • используйте эти числа как индекс в алфавите base64
  • угловой случай: при вводе байтового массива длина не делится на 3, а затем добавляется =или ==к результату

Решение, приведенное ниже, работает с 3-байтовыми фрагментами, поэтому оно подходит для больших массивов. Аналогичное решение для преобразования base64 в двоичный массив (без atob) ЗДЕСЬ


Мне нравится компактность, но преобразование в строки, представляющие двоичное число, а затем обратно, намного медленнее, чем принятое решение.
Гарр Годфри,

0

Используйте следующее, чтобы преобразовать массив 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);
        };


-1

Очень хороший подход к этому показан на веб-сайте 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 "☸☹☺☻☼☾☿"


-3

Если все, что вам нужно, это JS-реализация кодировщика base64, чтобы вы могли отправлять данные обратно, вы можете попробовать эту btoaфункцию.

b64enc = btoa(uint);

Несколько замечаний по btoa - это нестандартно, поэтому браузеры не обязаны его поддерживать. Однако большинство браузеров это делают. По крайней мере, большие. atobпротивоположное преобразование.

Если вам нужна другая реализация или вы обнаружите крайний случай, когда браузер не понимает, о чем вы говорите, поиск кодировщика base64 для JS не будет слишком сложным.

Думаю, их трое по какой-то причине торчат на сайте моей компании ...


Спасибо, я не пробовал это раньше.
Кайо Кето,

10
Пара заметок. btoa и atob на самом деле являются частью процесса стандартизации HTML5, и большинство браузеров уже поддерживают их практически таким же образом. Во-вторых, btoa и atob работают только со строками. Запуск btoa на Uint8Array сначала преобразует буфер в строку с помощью toString (). В результате получается строка «[объект Uint8Array]». Вероятно, это не то, что задумано.
kanaka

1
@CaioKeto, возможно, вы захотите изменить выбранный ответ. Это неправильный ответ.
kanaka

-4

npm install 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.jsнапишет в консоль AVMbY2Y = .


1
Забавно, что -veголосование за ответ принимается, а не высоко +ve.
Вишнудев
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.