将base64字符串转换为ArrayBuffer

207

我需要将一个Base64编码的字符串转换为ArrayBuffer。

这些Base64字符串是用户输入的,他们将从电子邮件中复制并粘贴,因此在页面加载时它们不在那里。

我想在JavaScript中完成这个操作,如果可能的话,不要通过ajax调用服务器。

我发现了一些有趣的链接,但它们没有帮助我:

ArrayBuffer to base64 encoded string

这是相反的转换,从ArrayBuffer到Base64,而不是相反。

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

这看起来不错,但我不知道如何使用代码。

有没有简单(或本地)的方法来进行转换?谢谢

13个回答

258
function base64ToArrayBuffer(base64) {
    var binaryString = atob(base64);
    var bytes = new Uint8Array(binaryString.length);
    for (var i = 0; i < binaryString.length; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
}

9
请解释一下这里到底发生了什么。 - Govinda Sakhare
7
很简单,首先我们解码base64字符串(使用atob),然后创建一个与解码后字符串长度相同的8位无符号整数新数组。接下来我们循环遍历该字符串,并将每个字符的Unicode值填充到数组中。 - Goran.it
9
这不正确。它允许JavaScript将字节解释为字符串,这会影响实际上是真正二进制的数据。 - Tomáš Zato
15
问题在于: a)并非每个字节序列都是有效的Unicode。 b)Unicode中的每个字符不一定只有一个字节,因此bytes[i] = binary_string.charCodeAt(i);可能是错误的。 - mixture
3
这个答案是正确的,我已经测试了所有可能的值。函数 window.atob 解码输入,然后将每个解码字节写入一个 UTF-16 字符中,该字符为 2 个字节。没有任何损失,因为一个字节只能达到 255。请注意,如果编码内容是 UTF-8 文本,则仍然需要解码:new TextDecoder("utf-8").decode(_base64ToArrayBuffer("4oKs")); - Florent B.
显示剩余5条评论

150
使用TypedArray.from
Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))

与Goran.it答案的for循环版本进行比较的性能。

3
喜欢这种一行代码的人要记住,“Uint8Array.from”在某些浏览器上仍然存在兼容性问题。 - IzumiSy
10
请勿推荐使用 atob 或 btoa 进行 Base64 编码和解码:https://developer.mozilla.org/zh-CN/docs/Web/API/WindowBase64/Base64_encoding_and_decoding - Kugel
7
иҝҷдёҚжҳҜдёҖдёӘж•°з»„зј“еҶІеҢәпјҢиҖҢжҳҜдёҖдёӘзұ»еһӢеҢ–ж•°з»„гҖӮдҪ еҸҜд»ҘйҖҡиҝҮиҝ”еӣһз»“жһңзҡ„.bufferеұһжҖ§жқҘи®ҝй—®ж•°з»„зј“еҶІеҢәпјҢеҗҺиҖ…жҳҜд»ҺUint8ArrayдёӯиҺ·еҸ–зҡ„гҖӮ - oligofren
14
@Saites,atobbtoa并没有问题,只需要给它们有效的输入。atob需要一个有效的base64字符串,否则它会抛出错误。而btoa需要一个有效的字节字符串(也称为二进制字符串),该字符串包含在0-255范围内的字符。如果您的字符串超出了该范围,btoa将会抛出一个错误。 - GetFree
1
atob 方法的问题在于它会破坏 Unicode 字符。例如, atob('8J+ZiA==') 返回 'ð\x9F\x99\x88',你需要解除破坏才能获得正确的 UTF8 字符串。但是,逐个字符调用 c.charCodeAt(0) 是可行的,并且您可以安全地调用 new TextDecoder.decode(uint8array) 并获得正确的 UTF8 字符串。 - ShortFuse
显示剩余6条评论

60

对于Node.js用户:

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

myBuffer将会是Buffer类型,它是Uint8Array的子类。不幸的是,Uint8Array并不是OP所问的ArrayBuffer。但是,当操作一个ArrayBuffer时,我几乎总是用Uint8Array或类似的东西进行封装,这样它应该接近所需的内容。


8
这段代码似乎并没有生成Uint8Array,因为使用此代码的函数在传入此调用的结果时会出错。但是,Uint8Array.from(Buffer.from(someBase64String,'base64'))可以很好地生成Uint8Array类型的值。请注意保持原文意思不变,并将翻译语言通俗易懂。 - LB2

46

由于JavaScript中的Unicode问题,Goran.it的答案无法使用- 参见https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding

我最终使用了Daniel Guerrero博客中提供的函数: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); 

1
该方法比使用atob快2倍。 - xiaoyu2er
6
你能举一个这种情况不适用的例子吗?文章讨论了编码任意字符串,其中可能包含Unicode字符,但这完全不适用于atob - riv
2
decodeArrayBuffer 返回一个大小总是可被3整除的 ArrayBuffer,我不确定这是设计还是一个bug。我会在Github项目中询问。 - ceztko
@ceztko 这可能是(无意中)设计的。Base64编码算法将3个字节的组转换为4个字符。解码方法可能会分配一个ArrayBuffer,其长度为base64String.length/4*3字节,并在完成时不会截断任何未使用的字节。 - AlwaysLearning
4
@AlwaysLearning 这意味着它可能存在错误,因为剩余的零字节可能会损坏预期的输出内容。 - ceztko
显示剩余2条评论

27

异步解决方案,在数据量大时更好:

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

15

Javascript是一个很好的开发环境,所以它不提供解决这个小问题似乎有点奇怪。本页面提供的其他解决方案可能潜在地较慢。以下是我的解决方案,它利用了内置功能解码base64图像和声音数据URL。

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

如果base64字符串格式不正确,发送请求将失败。

可能没有必要使用mime类型(application/octet)。

在Chrome中测试。 应该也能在其他浏览器中使用。


2
这对我来说是完美的解决方案,简单而干净。我在Firefox、IE 11和Edge中快速测试了它,运行良好! - cs-NET
我不确定在IE11中它是如何工作的,但我遇到了一个“访问被拒绝”的错误,这似乎是CORS限制。 - Sergiu
7
使用异步/等待和Fetch API,这可以更简洁地编写为await (await fetch("data:application/octet;base64," + base64data)).arrayBuffer() - Jordan Mann
太好了!我正在开发一个Angular应用程序,由于性能/优化问题,我不愿使用Node缓冲区。上面Jordan Mann提供的简化解决方案非常好!谢谢! - Nalin Jayasuriya

10

Pure JS - 无需字符串中间步骤(无 atob)

我编写了以下函数,以直接方式转换 base64(在中间步骤不进行字符串转换)。思路如下:

  • 获取 4 个 base64 字符块
  • 找到每个字符在 base64 字母表中的索引
  • 将索引转换为 6 位数字(二进制字符串)
  • 连接四个 6 位数字,得到 24 位数字(存储为二进制字符串)
  • 将 24 位字符串拆分为三个 8 位数字,并将每个数字转换为数字并将它们存储在输出数组中
  • 特殊情况:如果输入的 base64 字符串以一个或两个 = 字符结尾,则从输出数组中删除一个或两个数字

以下解决方案允许处理大型输入 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);

注意!

如果你想将base64解码为字符串(而不是字节数组),并且知道结果包含utf8字符,则通常情况下atob失败,例如对于字符8J+SqQ==atob("8J+SqQ==")会给出错误的结果。在这种情况下,你可以使用上述解决方案,并以正确的方式将结果字节数组转换为字符串,例如:

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 testB64 = "8J+SqQ==";   // for string: "";  
console.log('input base64            :', testB64);

let decodedBytes = base64ToBytesArr(testB64); // decode base64 to array of bytes
console.log('decoded bytes           :', JSON.stringify(decodedBytes));

let result = new TextDecoder("utf-8").decode(new Uint8Array(decodedBytes));
console.log('properly decoded string :', result);

let result_atob = atob(testB64);
console.log('decoded by atob         :', result_atob);

2022年8月4日测试代码片段的浏览器版本分别为:chrome 103.0.5060.134(arm64)、safari 15.2、firefox 103.0.1(64位)、edge 103.0.1264.77(arm64)和node-js v12.16.1。


so no missing "."? - Gillsoft AB
在浏览器中进行测试,我不确定这是否是预期结果? 《爱丽丝漫游奇境记》(即最后一个字符为NaN) - Gillsoft AB
1
@GillsoftAB 谢谢您提供这些信息 - 您是正确的 - 我已经修复了这个问题。 - Kamil Kiełczewski
@TefoD 上面的代码展示了输入和输出字符串的长度 - 我测试了几个案例 - 输入字符串的长度总是与输出字符串的长度相同。那么你如何检测输出末尾的多余bx00?(提供示例输入以及检测问题的方法) - Kamil Kiełczewski
@KamilKiełczewski,抱歉我的错 - 尾随的0零是来自你之前的一个函数 - 我将删除我之前的无意义评论。 - TefoD

3

我强烈建议使用一个正确实现base64规范的npm包。

我知道的最好的一个是rfc4648

问题在于,btoa和atob使用的是二进制字符串而不是Uint8Array,试图将其转换为和从中转换都很麻烦。此外,npm上有很多糟糕的包。在找到这个之前,我浪费了很多时间。

那个特定包的创建者做了一件简单的事情:他们采用了Base64规范(顺便说一下,规范在这里),并正确地从头到尾实现它(包括规范中其他格式,例如Base64-url、Base32等...)。这似乎不算什么,但显然这对其他库来说已经太难了。

所以,是的,我知道我正在进行一些劝诱,但如果你想避免浪费太多时间,就使用rfc4648吧。


2
我使用这个问题的被接受的答案来创建base64Url字符串和数组缓冲区之间的转换,这是在通过ASCII-cookie传输的base64Url数据领域(atob,btoa是base64[with + /]< ->js二进制字符串),因此我决定发布代码。许多人可能需要进行转换,并且客户端-服务器通信可能会使用base64Url版本(尽管cookie可能包含+/以及- _字符,如果我理解正确,则仅限于",;\字符和128 ASCII中的某些邪恶字符)。但是url不能包含/字符,因此更广泛地使用b64 url版本,当然不是atob-btoa支持的内容... 看到其他评论,我想强调我的用例是通过url / cookie传输base64Url数据,并尝试在js crypto api(2017)中使用此加密数据,因此需要ArrayBuffer表示和b64u<->arrBuff转换...如果数组缓冲区代表的内容与base64不同(ascii的一部分),则该转换将无法正常工作,因为atob,btoa仅限于ascii(128)。请查看适当的转换器,如下所示:buff ->b64u版本来自Mathias Bynens的推文,谢谢他(也是)!他还编写了一个base64编码器/解码器:https://github.com/mathiasbynens/base64。从Java来的人可能会帮助理解Java byte []实际上是JS Int8Array(有符号int),但我们在这里使用无符号版本Uint8Array,因为js conversions使用它们。它们都是256位,所以我们现在将其称为js中的byte []...此代码来自模块类,因此是静态的。
//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;
}

1

将Base64转换为ArrayBuffer:

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

我尝试使用上述代码,它可以正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接