Преобразовать строку base64 в ArrayBuffer

Мне нужно преобразовать строку кодирования base64 в ArrayBuffer. Строки base64 являются пользовательским вводом, они будут скопированы и вставлены из электронного письма, поэтому их не будет при загрузке страницы. Я хотел бы сделать это в javascript, не делая ajax-вызов на сервер, если это возможно.

Я нашел эти ссылки интересными, но они мне не помогли:

ArrayBuffer для base64 кодированной строки

речь идет об обратном преобразовании из ArrayBuffer в base64, а не наоборот

http://jsperf.com/json-vs-base64/2

это выглядит хорошо, но я не могу понять, как использовать код.

Есть ли простой (возможно, родной) способ сделать преобразование? Спасибо

15 ответов

Решение

Попробуй это:

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

Используя TypedArray.from:

Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))

Производительность сравнивать с циклической версией ответа Goran.it.

Для пользователей Node.js:

const myBuffer = Buffer.from(someBase64String, 'base64');

myBuffer будет иметь тип Buffer, который является подклассом Uint8Array. К сожалению, Uint8Array НЕ является ArrayBuffer, как просил OP. Но при манипулировании ArrayBuffer я почти всегда оборачиваю его Uint8Array или чем-то подобным, поэтому он должен быть близок к тому, что запрашивается.

Ответ Goran.it не работает из-за проблемы с юникодом в javascript - https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding.

В итоге я использовал функцию, приведенную в блоге Дэниела Герреро: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/

Функция указана в ссылке на github: https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js

Используйте эти строки

var uintArray = Base64Binary.decode(base64_string);  
var byteArray = Base64Binary.decodeArrayBuffer(base64_string); 

Только что нашёл base64-arraybuffer, небольшой пакет npm с невероятно высокой загрузкой, 5 миллионов загрузок в прошлом месяце (2017-08).

https://www.npmjs.com/package/base64-arraybuffer

Для тех, кто ищет что-то лучшее стандартное решение, это может быть.

Асинхронное решение, лучше, когда данные большие:

// base64 to buffer
function base64ToBufferAsync(base64) {
  var dataUrl = "data:application/octet-binary;base64," + base64;

  fetch(dataUrl)
    .then(res => res.arrayBuffer())
    .then(buffer => {
      console.log("base64 to buffer: " + new Uint8Array(buffer));
    })
}

// buffer to base64
function bufferToBase64Async( buffer ) {
    var blob = new Blob([buffer], {type:'application/octet-binary'});    
    console.log("buffer to blob:" + blob)

    var fileReader = new FileReader();
    fileReader.onload = function() {
      var dataUrl = fileReader.result;
      console.log("blob to dataUrl: " + dataUrl);

      var base64 = dataUrl.substr(dataUrl.indexOf(',')+1)      
      console.log("dataUrl to base64: " + base64);
    };
    fileReader.readAsDataURL(blob);
}

Javascript - это отличная среда разработки, поэтому она кажется странной, чем не дает решения этой небольшой проблемы. Решения, предлагаемые в других местах на этой странице, потенциально медленные. Вот мое решение. Он использует встроенную функциональность, которая декодирует URL-адреса base64 изображения и звуковых данных.

var req = new XMLHttpRequest;
req.open('GET', "data:application/octet;base64," + base64Data);
req.responseType = 'arraybuffer';
req.onload = function fileLoaded(e)
{
   var byteArray = new Int8Array(e.target.response);
   // var shortArray = new Int16Array(e.target.response);
   // var unsignedShortArray = new Int16Array(e.target.response);
   // etc.
}
req.send();

Запрос на отправку завершается неудачно, если строка base 65 неправильно сформирована.

Тип MIME (application/octet), вероятно, не нужен.

Проверено в хром. Должно работать в других браузерах.

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

Я пишу следующую функцию, которая напрямую преобразует base64 (без преобразования в строку на промежуточном этапе). ИДЕЯ

  • получить блок из 4 символов base64
  • найти индекс каждого символа в алфавите base64
  • преобразовать индекс в 6-битное число (двоичная строка)
  • присоединиться к четырем 6-битным числам, что дает 24-битное число (хранится как двоичная строка)
  • разделить 24-битную строку на три 8-битных и преобразовать каждую в число и сохранить их в выходном массиве
  • угловой случай: если входная строка base64 заканчивается на один / два = char, удалить одно / два числа из выходного массива

Нижеприведенное решение позволяет обрабатывать большие входные строки base64. Аналогичная функция для преобразования байтов в base64 без btoa ЗДЕСЬ

function base64ToBytesArr(str) {
  const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
  let result = [];

  for(let i=0; i<str.length/4; i++) {
    let chunk = [...str.slice(4*i,4*i+4)]
    let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join(''); 
    let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
    result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
  }
  return result;
}


// --------
// TEST
// --------


let test = "Alice's Adventure in Wonderland.";  

console.log('test string:', test.length, test);
let b64_btoa = btoa(test);
console.log('encoded string:', b64_btoa);

let decodedBytes = base64ToBytesArr(b64_btoa); // decode base64 to array of bytes
console.log('decoded bytes:', JSON.stringify(decodedBytes));
let decodedTest = decodedBytes.map(b => String.fromCharCode(b) ).join``;
console.log('Uint8Array', JSON.stringify(new Uint8Array(decodedBytes)));
console.log('decoded string:', decodedTest.length, decodedTest);

Я настоятельно рекомендую использовать пакет npm, правильно реализующий спецификацию base64.

Лучшее, что я знаю, это rfc4648

Проблема в том, что btoa и atob используют двоичные строки вместо Uint8Array, и попытки преобразовать в него и из него громоздки. Также для этого в npm есть много плохих пакетов. Я теряю много времени, прежде чем найду его.

Создатели этого конкретного пакета сделали простую вещь: они взяли спецификацию Base64 (который здесь , кстати) и реализовали его правильно с самого начала и до конца. (Включая в спецификацию другие форматы, которые также полезны, например, Base64-url, Base32 и т. Д.). Кажется, это не так уж много, но, очевидно, это было слишком много, чтобы просить кучу других библиотек.

Так что да, я знаю, что занимаюсь прозелитизмом, но если вы тоже хотите не терять время, просто используйте rfc4648.

new TextEncoder().encode("base64").buffer // is ArrayBuffer
new TextEncoder().encode("base64")        // is Uint8Array

На самом деле, вы можете кодировать / декодировать любую строку, а не только строку base64.

MDN: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder

Могу ли я использовать... https://caniuse.com/

Polyfill: https://github.com/inexorabletash/text-encoding

Я использовал принятый ответ на этот вопрос для создания преобразований base64Url string <-> arrayBuffer в области данных base64Url, передаваемых через ASCII-cookie [atob, btoa являются base64 [с +/] <-> двоичной строкой js], поэтому я решил разместить код.

Многие из нас могут захотеть, чтобы и конверсии, и взаимодействие клиент-сервер могли использовать версию base64Url (хотя файл cookie может содержать символы +/, а также -_, если я правильно понимаю, только символы ",;\ и некоторые неверные символы из 128 ASCII). запрещены). Но URL-адрес не может содержать символ /, поэтому более широкое использование версии URL-адреса b64, которая, конечно, не поддерживается atob-btoa...

Видя другие комментарии, я хотел бы подчеркнуть, что мой вариант использования здесь — это передача данных base64Url через url/cookie и попытка использовать эти криптографические данные с js crypto api (2017), поэтому необходимо представление ArrayBuffer и преобразования b64u <-> arrBuff ... если буферы массива представляют что-то отличное от base64 (часть ascii), это преобразование не будет работать, поскольку atob, btoa ограничены ascii(128). Проверьте соответствующий конвертер, как показано ниже:

Версия buff -> b64u взята из твита Матиаса Байненса, спасибо и за него (тоже)! Он также написал кодировщик/декодер base64:https://github.com/mathiasbynens/base64 .

Исходя из java, это может помочь при попытке понять код, что java byte[] практически является js Int8Array (signed int), но мы используем здесь неподписанную версию Uint8Array, поскольку с ними работают преобразования js. Они оба 256-битные, поэтому теперь мы называем это byte[] в js...

Код из класса модуля, поэтому статический.

      //utility

/**
 * Array buffer to base64Url string
 * - arrBuff->byte[]->biStr->b64->b64u
 * @param arrayBuffer
 * @returns {string}
 * @private
 */
static _arrayBufferToBase64Url(arrayBuffer) {
    console.log('base64Url from array buffer:', arrayBuffer);

    let base64Url = window.btoa(String.fromCodePoint(...new Uint8Array(arrayBuffer)));
    base64Url = base64Url.replaceAll('+', '-');
    base64Url = base64Url.replaceAll('/', '_');

    console.log('base64Url:', base64Url);
    return base64Url;
}

/**
 * Base64Url string to array buffer
 * - b64u->b64->biStr->byte[]->arrBuff
 * @param base64Url
 * @returns {ArrayBufferLike}
 * @private
 */
static _base64UrlToArrayBuffer(base64Url) {
    console.log('array buffer from base64Url:', base64Url);

    let base64 = base64Url.replaceAll('-', '+');
    base64 = base64.replaceAll('_', '/');
    const binaryString = window.atob(base64);
    const length = binaryString.length;
    const bytes = new Uint8Array(length);
    for (let i = 0; i < length; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }

    console.log('array buffer:', bytes.buffer);
    return bytes.buffer;
}

сделал ArrayBuffer из base64:

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

Я пытался использовать приведенный выше код, и он отлично работает.

(2023) Я прочитал все ответы и протестировал все решения:

  1. &
  2. &
  3. ручная реализация JavaScript

Ниже мой результат теста:

из буфера массива в base64:

btoaне может обработать большой ввод (>1 МБ или около того), выдаст ошибку «Превышен максимальный размер стека вызовов».

очень хорошо.

Реализация Javascript работает, но в 10 раз медленнее, чем .

Итак, явный победительFileReader, ниже приведен наиболее читаемый способ его написания:

      async function arrayBufferToBase64(buffer) {
  const file = new File([buffer], "temp.bin", {
    type: "application/octet-stream",
  });
  const fileReader = new FileReader();
  const dataUrl = await new Promise((resolve, reject) => {
    fileReader.onload = () => resolve(fileReader.result);
    fileReader.onerror = () => reject(fileReader.error);
    fileReader.readAsDataURL(file);
  });
  return dataUrl.split("base64,")[1];
}

из base64 в буфер массива:

Реализация JavaScript работает лучше всего.

fetchработает, но в 10 раз медленнее.

atobработает, но еще медленнее.

Таким образом, реализация JavaScript является явным победителем. Вот что я использовал:

      import { decode } from "base64-arraybuffer";

const buffer = decode(base64String);

Если вам не нравится устанавливать другой пакет, вы можете скопировать src-код , он занимает около 30 строк.

Решение без

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

На странице MDN о Base64 есть решение без их использования . Ниже вы можете найти код для преобразования строки base64 в массив Uint8Array, скопированный из документации.

Обратите внимание, что приведенная ниже функция возвращает массив Uint8Array. Чтобы получить версию ArrayBuffer, вам просто нужно сделатьuintArray.buffer.

      function b64ToUint6(nChr) {
  return nChr > 64 && nChr < 91
    ? nChr - 65
    : nChr > 96 && nChr < 123
    ? nChr - 71
    : nChr > 47 && nChr < 58
    ? nChr + 4
    : nChr === 43
    ? 62
    : nChr === 47
    ? 63
    : 0;
}

function base64DecToArr(sBase64, nBlocksSize) {
  const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
  const nInLen = sB64Enc.length;
  const nOutLen = nBlocksSize
    ? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
    : (nInLen * 3 + 1) >> 2;
  const taBytes = new Uint8Array(nOutLen);

  let nMod3;
  let nMod4;
  let nUint24 = 0;
  let nOutIdx = 0;
  for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
    nMod4 = nInIdx & 3;
    nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
    if (nMod4 === 3 || nInLen - nInIdx === 1) {
      nMod3 = 0;
      while (nMod3 < 3 && nOutIdx < nOutLen) {
        taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
        nMod3++;
        nOutIdx++;
      }
      nUint24 = 0;
    }
  }

  return taBytes;
}

Если вас интересует обратная операция, ArrayBuffer в base64, вы можете найти, как это сделать, по той же ссылке.

Результатом atob является строка, разделенная запятой.

,

Более простой способ - преобразовать эту строку в строку массива json, а после этого проанализировать ее в byteArray ниже, код можно просто использовать для преобразования base64 в массив чисел

      let byteArray = JSON.parse('['+atob(base64)+']'); 
let buffer = new Uint8Array(byteArray);
Другие вопросы по тегам