如何将uint8数组转换为base64编码的字符串?


Answers:


15

已经提出的所有解决方案都存在严重问题。一些解决方案无法在大型阵列上运行,某些解决方案提供错误的输出,如果中间字符串包含多字节字符,则某些解决方案会在btoa调用上引发错误,有些解决方案会消耗比所需更多的内存。

因此,我实现了直接转换功能,该功能无论输入如何都可以正常工作。它在我的机器上每秒转换约500万个字节。

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


将base64abc作为字符串数组的速度是否比仅使其成为字符串快?"ABCDEFG..."
Garr Godfrey

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 polyfill

如果您的数据包含纯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 2014年

3
@MichaelPaulukonis我的猜测是实际上是String.fromCharCode.apply导致了堆栈大小的超出。如果您的Uint8Array非常大,则可能需要迭代地构建字符串,而不是使用apply这样做。apply()调用会将数组的每个元素作为参数传递给fromCharCode,因此,如果数组长度为128000字节,则您将尝试使用128000个参数进行函数调用,这很可能会破坏堆栈。
kanaka 2014年

4
谢谢。我所需要的是btoa(String.fromCharCode.apply(null, myArray))
格伦·利特尔

29
如果字节数组不是有效的Unicode,则此方法无效。
Melab

11
base64字符串或中没有多字节字符 Uint8ArrayTextDecoder在这里使用绝对是错误的事情,因为如果您的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

完美的解决方案
Haris ur Rehman

2
使用RangeError: Maximum call stack size exceeded
Maxim Khokhryakov

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浏览器中,我在300kb +的缓冲区上计时,发现像分块一样慢地进行操作比逐字节进行处理要慢得多。这让我感到惊讶。
马特

@马特有趣。同时,Chrome现在可能会检测到这种转换,并对其进行了特定的优化,对数据进行分块处理可能会降低其效率。
kanaka

2
这不安全,对吗?如果我的块的边界切入了多字节UTF8编码的字符,那么fromCharCode()将无法从边界两侧的字节创建明智的字符,是吗?
詹斯(Jens)

2
@JensString.fromCharCode.apply()方法无法重现UTF-8:UTF-8字符的长度可能从一个字节到四个字节String.fromCharCode.apply()不等,但会检查UInt8的分段中的UInt8Array,因此错误地假设每个字符长为一个字节,并且与相邻字符无关那些。如果输入UInt8Array中编码的字符都恰好在ASCII(单字节)范围内,则它会偶然起作用,但不能重现完整的UTF-8。您需要为此使用TextDecoder或类似的算法
Jamie Birch

1
@Jens二进制数据数组中哪些多字节UTF8编码字符?我们在这里不处理unicode字符串,而是处理任意二进制数据,不应将其视为utf-8代码点。
RIV


0

这是一个JS函数:

之所以需要此功能,是因为Chrome在pushManager.subscribe中仍不接受base64编码的字符串作为applicationServerKey的值,而 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)的类似解决方案在这里


我喜欢紧凑性,但是将其转换为代表二进制数的字符串然后再返回比接受的解决方案要慢得多。
Garr Godfrey

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开发人员网络网站上显示了一种非常好的方法:

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

如果您想要的只是base64编码器的JS实现,以便可以将数据发送回去,则可以尝试使用该btoa功能。

b64enc = btoa(uint);

关于btoa的几点快速注释-它是非标准的,因此不会强迫浏览器支持它。但是,大多数浏览器都可以。大人物,至少。atob是相反的转换。

如果您需要其他实现,或者发现浏览器不知道您在说什么的极端情况,那么为JS搜索base64编码器就不会太困难。

由于某种原因,我认为其中有3个在我公司的网站上闲逛...


谢谢,我以前没有尝试过。
Caio Keto

10
几个笔记。btoa和atob实际上是HTML5标准化过程的一部分,大多数浏览器已经以几乎相同的方式支持它们。其次,btoa和atob仅适用于字符串。在Uint8Array上运行btoa首先将使用toString()将缓冲区转换为字符串。这将导致字符串“ [object Uint8Array]”。那可能不是预期的。
kanaka 2012年

1
@CaioKeto,您可能需要考虑更改所选答案。这个答案是不正确的。
kanaka 2014年

-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.jsAVMbY2Y =写入控制台。


1
一个-ve投票的答案被接受而不是一个高度的答案是很有趣的+ve
Vishnudev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.