ArrayBuffer转换为base64编码的字符串

323

我需要一种高效的(即本地实现)方式将ArrayBuffer转换为Base64字符串,该字符串需要在多部分post中使用。


如果你需要以multipart/form-data格式发送数据,那么你可以从ArrayBuffer创建一个FormData和一个Blob,将Blob附加到FormData中,以二进制形式发送数据,而不是使用base64编码。 - undefined
19个回答

345

18
我尝试使用链接中的非原生实现,将一个1M大小的缓冲区转换需要1分30秒的时间,而上方的循环代码只需要1秒钟。 - cshu
4
我喜欢这种方法的简单性,但所有的字符串拼接可能会很耗费时间。看起来,在Firefox、IE和Safari上建立字符数组并在最后使用join()函数连接它们的速度要快得多(但在Chrome上要慢得多):http://jsperf.com/tobase64-implementations - JLRishe
1
我正在尝试使用AngularJS和WebAPI2上传50MB的PDF文件。我正在使用上述代码行,在上传文件后,页面崩溃并挂起。我使用了下面的代码行,但在WebAPI方法中获取到null值。 “var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));”请提供任何想法... - prabhakaran S
4
我想知道为什么每个人都避免使用本地缓冲区的 toString('base64') 方法。 - João Eduardo
18
因为不是每个人都在使用 Node,所以浏览器中不存在 Node 的“Buffer”。 - Andrew
显示剩余10条评论

184

这对我来说很好用:

var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
在ES6中,语法略微简化:

const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));

正如评论所指出的那样,当ArrayBuffer很大时,此方法可能会导致某些浏览器运行时错误。无论如何,确切的大小限制在任何情况下均取决于实现。


61
我更喜欢这种简洁的方法,但出现了“最大调用栈大小超过”的错误。前面提到的循环技巧可以避免这个问题。 - Jason
15
我也遇到了堆栈大小错误,所以我使用了mobz的答案,效果很好。 - David Jones
26
对于大缓冲区它不起作用。稍作修改即可:btoa([].reduce.call(new Uint8Array(bufferArray),function(p,c){return p+String.fromCharCode(c)},'')) - laggingreflex
9
btoa 对于代码范围在 0-255 的字符是安全的,因为这里的情况如此(考虑 Uint8Array 中的 8)。 - GOTO 0
2
就我所知,在Chrome 104上使用...比使用apply要慢得多。 - JP Sugarbroad
显示剩余6条评论

59

对于那些喜欢简短的人,这里有另一个使用Array.reduce的示例,它不会导致堆栈溢出:

var base64 = btoa(
  new Uint8Array(arrayBuffer)
    .reduce((data, byte) => data + String.fromCharCode(byte), '')
);

10
不确定那是否真的性感。毕竟,您正在创建缓冲区中的 <字节数量> 个新字符串。 - Neonit
1
btoa(new Uint8Array(arraybuffer).reduce((data,byte)=>(data.push(String.fromCharCode(byte)),data),[]).join('')) 怎么样? - Roy Tinker
2
另一种选择:btoa(Array.from(new Uint8Array(arraybuffer)).map(b => String.fromCharCode(b)).join('')) - e741af0d41bc74bf854041f1fbdbf
1
在“Window”上执行“btoa”失败:要编码的字符串包含拉丁1范围之外的字符。 - RouR

45

还有一种使用Blob和FileReader的异步方式。

我没有测试性能,但这是一种不同的思考方式。

function arrayBufferToBase64( buffer, callback ) {
    var blob = new Blob([buffer],{type:'application/octet-binary'});
    var reader = new FileReader();
    reader.onload = function(evt){
        var dataurl = evt.target.result;
        callback(dataurl.substr(dataurl.indexOf(',')+1));
    };
    reader.readAsDataURL(blob);
}

//example:
var buf = new Uint8Array([11,22,33]);
arrayBufferToBase64(buf, console.log.bind(console)); //"CxYh"

使用dataurl.split(',',2)[1]替代dataurl.substr(dataurl.indexOf(',')+1) - Carter Medlin
3
这似乎不能保证能够正常工作。根据 https://w3c.github.io/FileAPI/#issue-f80bda5b ,readAsDataURL理论上可能会返回一个百分号编码的dataURI(而且在jsdom中似乎确实是这种情况)。 - T S
@CarterMedlin 为什么使用 splitsubstring 更好呢? - T S
split函数虽然简短,但dataurl可能包含一个或多个逗号(,),因此split不安全。 - cuixiping

41
OP没有指定运行环境,但如果你使用的是Node.JS,有一种非常简单的方法可以做到这一点。
根据官方的Node.JS文档: https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings
// This step is only necessary if you don't already have a Buffer Object
const buffer = Buffer.from(yourArrayBuffer);

const base64String = buffer.toString('base64');

此外,如果您正在使用Angular等框架,Buffer类也将在浏览器环境中提供。

你的回答仅适用于NodeJS,无法在浏览器中使用。 - jvatic
5
我明白了,原帖并没有清楚地指明运行环境,所以我的回答并不是错误的,他只标记了Javascript。因此,我更新了我的答案,使其更加简洁。我认为这是一个重要的答案,因为当时我也在搜索如何解决这个问题,但找不到最好的答案。 - João Eduardo
我最近意识到问题日期早于NodeJS本身,这更加说明版主应该在问题后添加附录,因为现在大多数人都在寻找NodeJS的解决方案,并且会被旧答案的流行所误导。 - João Eduardo

38

这个例子使用内置的 FileReader readDataURL() 来进行转换为 base64 编码。数据 URL 的结构是 data:[<mediatype>][;base64],<data>,因此我们在逗号处拆分该 URL 并仅返回 base64 编码的字符。

const blob = new Blob([array]);        
const reader = new FileReader();

reader.onload = (event) => {
  const dataUrl = event.target.result;
  const [_, base64] = dataUrl.split(','); 
  // do something with base64
};
   
reader.readAsDataURL(blob);

或者作为一个Promise化的实用工具:

async function encode(array) {
  return new Promise((resolve) => {
    const blob = new Blob([array]);
    const reader = new FileReader();
    
    reader.onload = (event) => {
      const dataUrl = event.target.result;
      const [_, base64] = dataUrl.split(',');
      
      resolve(base64);
    };
    
    reader.readAsDataURL(blob);
  });
}

const encoded = await encode(typedArray);

4
请问您需要对哪段代码进行翻译呢? - Oscar
类似于有用的MDN示例 - spenceryue
1
这绝对是最快的方法——在我的有限测试中比其他方法快十倍。 - jitin
5
为了得到纯粹的Base64字符串,我认为你需要删除DataURL头(data:*/*;base64,)。参考MDN文档 - Larry K
如何将base64转换回数组(在使用此方法对其进行编码后)? - Metric Rat
显示剩余2条评论

24

我用过这个,对我有用。

function arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}



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

不安全。请查看@chemoish的答案。 - Kugel
4
这是我找到的唯一解决方案,对我很有效。 - se22as
1
这是最易读的解决方案。 - Waruyama

18

我建议不要使用原生的btoa策略——因为它们无法正确编码所有的ArrayBuffer...

重写DOM的atob()和btoa()

由于DOM字符串是16位编码的字符串,在大多数浏览器中,在Unicode字符串上调用window.btoa将导致字符超出8位ASCII编码字符范围而引发异常。

虽然我从未遇到过这个确切的错误,但我发现许多我尝试编码的ArrayBuffer都没有正确编码。

我建议使用MDN或者gist。


btoa 不适用于字符串,但 OP 正在询问 ArrayBuffer。 - tsh
3
非常赞同,这里有很多建议错误的片段!我已经多次看到人们盲目使用atob和btoa而导致的错误。 - Kugel
15
使用其他答案中的策略可以很好地编码所有数组缓冲区,atob/btoa 只对包含大于 0xFF 的字符的文本造成问题(按定义字节数组不会包含这些字符)。MDN 警告并不适用,因为使用其他答案中的策略可以保证您得到的字符串只包含 ASCII 字符,因为 Uint8Array 中的任何值都保证在 0 和 255 之间,这意味着 String.fromCharCode 保证返回一个未超出范围的字符。 - Jamesernator
当btoa或Buffer不可用时(react-native),这是正确的答案。 - cancerbero

16

下面是两个简单的函数,用于将Uint8Array转换为Base64字符串,以及将其再次转回Uint8Array。

arrayToBase64String(a) {
    return btoa(String.fromCharCode(...a));
}

base64StringToArray(s) {
    let asciiString = atob(s);
    return new Uint8Array([...asciiString].map(char => char.charCodeAt(0)));
}

2
这是一个令人困惑的答案。那看起来不像是有效的JavaScript,而Uint8Array是ArrayBuffer吗? - user1153660
@user1153660 添加 function 关键字,它应该可以在现代浏览器中正常工作。 - Yeti
太棒了!btoa(String.fromCharCode(...a)); 是我目前看到的最短的编码Uint8Array的版本。 - Nicolo
4
这看起来不错,但如果数组太大,就会抛出“超过最大调用栈大小”的错误。 - Pawel Uchida-Psztyc

5

如果您可以添加一个库,可以使用base64-arraybuffer

yarn add base64-arraybuffer

然后:

  • encode(buffer) - 将ArrayBuffer编码为base64字符串
  • decode(str) - 将base64字符串解码为ArrayBuffer

1
最好的答案对我来说是因为它包括解码。 - fjsj

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