跳到主要内容

技巧 | 03 JavaScript 中将 ArrayBuffer 转换为字符串

· 阅读需 8 分钟
木易(OwenYang)
互联网人、电子垃圾体验者、挨踢FEer、木易跟打器作者

在 JavaScript 中将 ArrayBuffer 转换为字符串,可以使用 TextDecoder API。TextDecoder 可从字节序列中解码文本内容,支持多种编码格式。

以下是将 ArrayBuffer 转换为字符串的示例代码:

// 假设 ArrayBuffer 对象为 buffer
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(buffer);

在上面的代码中,我们创建了一个 TextDecoder 对象,使用 utf-8 编码对 ArrayBuffer 进行解码,并将解码后的文本存储在变量 text 中。

如果 ArrayBuffer 中存储的是 GB2312 编码的文本,可以将 utf-8 编码修改为 gb2312

您还可以封装为函数,以便于使用:

function arrayBufferToString(buffer, encoding = 'utf-8') {
const decoder = new TextDecoder(encoding);
return decoder.decode(buffer);
}

这个函数接收两个参数,第一个参数表示要转换的 ArrayBuffer 对象,第二个参数为编码格式(默认为'utf-8')。 返回转换后的字符串。调用该函数的方式如下所示:

const buffer = new ArrayBuffer(2);
const intArray = new Uint8Array(buffer);
intArray[0] = 72;
intArray[1] = 105;

const str = arrayBufferToString(buffer);
console.log(str); // Output: Hi

01 实际问题 - 网页乱码

nodejs 使用 axios 写爬虫时 Response 乱码,经查查发现网页编码是 gb2312的。则我们可以通过返回 arraybuffer 的方式,再重新编码即可。

乱码
const { data } = await axios.get('http://xxx.y.z', { responseType: 'arraybuffer'});

const normalStrig = arrayBufferToString(Buffer.from(data), 'gb2312'); // 返回正确的字符串

02 技术细节 - ArrayBuffer、Int32Array等

ArrayBuffer 是一种用于在 JavaScript 中存储二进制数据的对象,可以看做是一个固定大小的字节缓冲区。可以使用 ArrayBuffer 来存储任意类型的二进制数据,包括数字、图像、音频等等。

Int32Array 是一种类型化数组(TypedArray),它只能存储 32 位整数类型的数据。具体来说,Int32Array 可以存储范围在 -2147483648 ~ 2147483647 之间的整数数据,也就是 JavaScript 中的 32 位有符号整数类型。

Int32Array 中每一项都占用 4 个字节,使用 Int32Array 对象可以快速地读取和写入 ArrayBuffer 中的 32 位整数数据,适合处理大量数据的场景。

除了 Int32Array,还有一些其他的类型化数组也可以用于存储不同类型的数据,包括:

  • Int8Array:1 个字节的有符号整数类型,范围在 -128 ~ 127 之间;
  • Uint8Array:1 个字节的无符号整数类型,范围在 0 ~ 255 之间;
  • Uint16Array:2 个字节的无符号整数类型,范围在 0 ~ 65535 之间;
  • Int16Array:2 个字节的有符号整数类型,范围在 -32768 ~ 32767 之间;
  • Uint32Array:4 个字节的无符号整数类型,范围在 0 ~ 4294967295 之间;
  • Float32Array:4 个字节的单精度浮点数类型;
  • Float64Array:8 个字节的双精度浮点数类型。

以上这些类型化数组都只能存储指定类型的数据,并且每一项占用的字节数都是固定的。使用类型化数组可以轻松地读取和写入 ArrayBuffer 中指定类型的数据,提高数据读写的效率。

03 技术举例

Uint8Array 和 Uint32Array 都是类型化数组(TypedArray),但它们的应用场景和使用方式有所不同。

Uint8Array 适用于存储任意的 8 位无符号整数类型的数据,每一项占用一个字节。可以通过数组下标的方式直接访问和修改其中的数据。

以下是 Uint8Array 的一个例子,使用它来将一个字符串编码成 UTF-8 的字节数组:

function encodeUTF8(str) {
const codePoints = Array.from(str, c => c.codePointAt(0));
const buffer = new ArrayBuffer(codePoints.length * 4);
const uint8Array = new Uint8Array(buffer);
let offset = 0;
for (let i = 0; i < codePoints.length; i++) {
const codePoint = codePoints[i];
if (codePoint < 0x80) {
uint8Array[offset++] = codePoint;
} else if (codePoint < 0x800) {
uint8Array[offset++] = 0xC0 | (codePoint >> 6);
uint8Array[offset++] = 0x80 | (codePoint & 0x3F);
} else if (codePoint < 0x10000) {
uint8Array[offset++] = 0xE0 | (codePoint >> 12);
uint8Array[offset++] = 0x80 | ((codePoint >> 6) & 0x3F);
uint8Array[offset++] = 0x80 | (codePoint & 0x3F);
} else {
uint8Array[offset++] = 0xF0 | (codePoint >> 18);
uint8Array[offset++] = 0x80 | ((codePoint >> 12) & 0x3F);
uint8Array[offset++] = 0x80 | ((codePoint >> 6) & 0x3F);
uint8Array[offset++] = 0x80 | (codePoint & 0x3F);
}
}
return uint8Array.subarray(0, offset);
}

const str = "Hello, 世界!";
const byteArr = encodeUTF8(str);
console.log(byteArr);

在上面的代码中,我们通过 new Uint8Array(buffer) 创建了一个长度为 codePoints.length * 4 的 Uint8Array 对象 uint8Array,即总共分配了足够存储 UTF-8 字节数组的缓存空间。然后通过对字符的 Unicode 编码进行判断,将每个字符转换为对应的 UTF-8 字节序列,并存储到 uint8Array 中。最后通过 uint8Array.subarray(0, offset) 返回仅包含有效数据的 Uint8Array 视图对象。

Uint32Array 适用于存储任意的 32 位无符号整数类型的数据,每一项占用 4 个字节。可以通过数组下标的方式直接访问和修改其中的数据。

好的,以下是一个使用 Uint32Array 在 JavaScript 中实现求素数的例子:

// 埃拉托斯特尼(Eratosthenes)筛法
function sieveOfEratosthenes(n) {
const primes = new Uint32Array(n + 1).fill(1);
primes[0] = 0;
primes[1] = 0;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (primes[i]) {
for (let j = i * i; j <= n; j += i) {
primes[j] = 0;
}
}
}
return primes;
}

const n = 100;
const primes = sieveOfEratosthenes(n);

for (let i = 0; i < primes.length; i++) {
if (primes[i]) {
console.log(i);
}
}

上面的代码演示了如何使用 Uint32Array 在 JavaScript 中实现求小于等于 n 的所有素数的过程。我们创建一个长度为 n+1 的 Uint32Array 缓冲区 primes,它的每个元素都初始化为 1,表示初始时假设所有数都是素数。

然后从 2 开始,我们遍历到 n 的平方根,如果某个数 i 是素数,就将大于 i 的 i 的倍数都标记为非素数(将 primes[j] 置为 0),最终就能够得到所有小于等于 n 的素数。

使用 Uint32Array 能够支持处理更大的整数,也能够减少内存空间的使用,提高代码执行效率。这个例子说明了 Uint32Array 可以应用于数论问题中,并且通过使用 JavaScript 的更高级语言功能可以加速计算。