Як перетворити масив 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).
Michael Paulukonis

3
@MichaelPaulukonis, я припускаю, що насправді саме String.fromCharCode.apply призводить до перевищення розміру стека. Якщо у вас дуже великий Uint8Array, вам, ймовірно, доведеться ітеративно будувати рядок, замість того, щоб використовувати для цього застосувати. Виклик apply () передає кожен елемент вашого масиву як параметр fromCharCode, тому, якщо масив має довжину 128000 байт, тоді ви намагаєтесь здійснити виклик функції з 128000 параметрами, який, ймовірно, призведе до збиття стека.
канака

4
Дякую. Все, що мені потрібно булоbtoa(String.fromCharCode.apply(null, myArray))
Глен Літл

29
Це не працює, якщо масив байтів не є дійсним Unicode.
Мелаб,

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

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

Ідеальне рішення
Haris ur Rehman

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 кб і виявив, що це робиться шматками, як ви, щоб бути дещо повільнішим, ніж робити це байт за байтом. Це мене здивувало.
Метт

@Matt цікаво. Можливо, тим часом Chrome тепер виявив це перетворення і має для нього конкретну оптимізацію, і шматування даних може знизити його ефективність.
канака

2
Це не безпечно, правда? Якщо межа мого фрагмента прорізає багатобайтовий кодований символ UTF8, тоді fromCharCode () не зможе створити розумні символи з байтів по обидва боки межі, чи не так?
Йенс,

2
String.fromCharCode.apply()Методи @Jens не можуть відтворити UTF-8: довжина символів UTF-8 може варіюватися від одного байта до чотирьох байтів, проте String.fromCharCode.apply()аналізує UInt8Array у сегментах UInt8, тому помилково передбачається, що кожен символ повинен бути рівним одному байту і не залежить від сусіднього ті. Якщо символи, закодовані у вхідному UInt8Array, знаходяться в діапазоні ASCII (однобайтовий), це спрацює випадково, але не може відтворити повний UTF-8. Для цього вам потрібен TextDecoder або подібний алгоритм .
Jamie Birch

1
@Jens, які багатобайтові символи, закодовані UTF8, у двійковому масиві даних? Тут ми маємо справу не з рядками unicode, а з довільними двійковими даними, які НЕ повинні розглядатися як кодові точки utf-8.
рів

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
Barry Michael Doyle

0

Чистий JS - без проміжного кроку (без btoa)

У наведеному нижче рішенні я опускаю перетворення в рядок. IDEA виконує такі дії:

  • об'єднайте 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 не буде надто складним.

Думаю, на веб-сайті моєї компанії з якихось причин тусуються 3 з них ...


Дякую, я не пробував цього раніше.
Кайо Кето

10
Пара нотаток. btoa та atob насправді є частиною процесу стандартизації HTML5, і більшість браузерів вже підтримують їх майже однаково. По-друге, btoa та atob працюють лише з рядками. Запуск btoa на Uint8Array спочатку перетворить буфер у рядок за допомогою toString (). Це призводить до появи рядка "[об'єкт Uint8Array]". Це, мабуть, не те, що призначено.
канака

1
@CaioKeto Ви можете розглянути можливість зміни обраної відповіді. Ця відповідь не правильна.
канака 03.03.14

-4

npm встановити 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.
Vishnudev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.