如何在 JavaScript 中将 ArrayBuffers 与 DataView 一起使用

How to use ArrayBuffers with DataViews in JavaScript

我看到的关于 ArrayBuffer 的唯一真实教程来自 HTML5Rocks. But I am wondering specifically how to manipulate the individual bytes. For example, this cartoon,Mozilla 的 ArrayBuffers 显示了包裹在 Uint8Array 视图中的 ArrayBuffer 的图像:

给人的感觉是你可以用 ArrayBuffer 做到这一点:

var x = new ArrayBuffer(10)
x[0] = 1
x[1] = 0
...
x[9] = 1

即手动设置字节。但我还没有看到任何关于此类功能的文档。相反,您似乎应该使用 TypedArray 组件之一或 DataView:

var x = new ArrayBuffer(100)
var y = new DataView(x)
y.setUint32(0, 1)
console.log(y.getUint32(0)) // 1
console.log(x[0]) // undefined

但在使用 DataView 操作 ArrayBuffer 之后,您似乎无法直接访问 ArrayBuffer 上的任何字节。

用 ArrayBuffer 和 DataView 尝试其他东西我感到困惑:

var x = new ArrayBuffer(100)
var y = new DataView(x)
y.setUint32(0, 1)
y.setUint32(1, 2)
console.log(y.getUint32(0)) // 0 (incorrect)
console.log(y.getUint32(1)) // 2 (correct)

var x = new ArrayBuffer(100)
var y = new DataView(x)
y.setUint32(0, 1)
y.setUint32(2, 2)
console.log(y.getUint32(0)) // 0 (incorrect)
console.log(y.getUint32(1)) // 0 (?)
console.log(y.getUint32(2)) // 2 (correct)

var x = new ArrayBuffer(100)
var y = new DataView(x)
y.setUint32(0, 1)
y.setUint32(3, 2)
console.log(y.getUint32(0)) // 0 (incorrect)
console.log(y.getUint32(1)) // 0 (?)
console.log(y.getUint32(2)) // 0 (?)
console.log(y.getUint32(3)) // 2 (correct)

直到最后我得到与 32 字节视图对齐的 4 字节。但接下来就更奇怪了:

var x = new ArrayBuffer(100)
var y = new DataView(x)
y.setUint32(0, 1)
y.setUint32(4, 2)
console.log(y.getUint32(0)) // 1
console.log(y.getUint32(1)) // 256
console.log(y.getUint32(2)) // 65536
console.log(y.getUint32(3)) // 16777216
console.log(y.getUint32(4)) // 2

这告诉我需要手动将 32 位值放在适当的位置,但我不明白为什么会出现其他值,例如 256 和 65536。

接下来,我希望能够将字节打印为 101011100100 等,整个 ArrayBuffer,或者只是它的一部分。

最后,我希望能够对 8、16 和 32 位以外的值进行编码,例如 base64,或 4 位,或奇数位。没有用于执行此操作的通用 DataView API,例如通用 y.setUint(bitsCount, offset, value),不知道为什么。

总而言之,在处理低级位管理时,有很多我不熟悉的地方。但是,我想学习如何使用它。因此,如果有人可以快速展示如何获得 ArrayBuffer + DataView 组合的工作知识,那将非常有帮助。

我了解如何使用 Uint8Array 和相关的 TypedArray,我只是想学习如何使用较低级别的 ArrayBuffer。

AFAIK DataView 并不真正适合您使用它的目的。它基本上用于解析或创建已知格式的二进制文件并处理字节序问题。特别是它处理字节顺序问题,它允许您读取未对齐的 16 位和 32 位值。换句话说,Float32Array 缓冲区中的偏移量将是 4 的倍数,因为 32 位浮点数是 4 个字节。无法在非 4 字节边界上对齐 Float32Array。使用 DataView 传入偏移量,它可以在任何字节边界处传递。

DataView 也比相同 API 的纯 JavaScript 实现慢,至少目前是这样。

因此,对于您的用例,您可能不想使用 DataView,而是自己制作。

此外,操作随机大小的位字符串并不是很常见的需求,因此如果您想按位读写,您将不得不编写自己的库。

ArrayBuffer 只是一个缓冲区。您无法直接访问其内容。您必须在该缓冲区中创建一个 ArrayBufferView。有许多不同类型的视图,例如 Int8ArrayUint32ArrayFloat32ArrayDataView。他们可以查看全部或部分 ArrayBuffer。他们还可以创建自己的 ArrayBuffer.

const view = new Uint32Array(100);

一模一样
const view = new Uint32Array(new ArrayBuffer(400));

您可以通过 buffer 属性 访问任何视图的 ArrayBuffer。所以这 3 行

const buffer = new ArrayBuffer(400);
const view1 = new Uint32Array(buffer);
const view2 = new Uint8Array(buffer);

与这两行相同

const view1 = new Uint32Array(100);
const view2 = new Uint8Array(view1.buffer);

至于你的第一个例子传递给DataView的偏移量以字节为单位所以这个

var x = new ArrayBuffer(100)
var y = new DataView(x)
y.setUint32(0, 1)
y.setUint32(1, 2)
console.log(y.getUint32(0)) // 0 (incorrect)
console.log(y.getUint32(1)) // 2 (correct)

第一个 y.setUint32(0, 1) 是设置缓冲区的前 4 个字节。字节 0、1、2 和 3。第二个 y.setUint32(1, 2) 设置字节 1、2、3、4。因此您要覆盖字节 1、2 和 3。

var x = new ArrayBuffer(100)

// x is 100 bytes `[0, 0, 0, 0, 0, 0, 0 ....`

var y = new DataView(x)
y.setUint32(0, 1);  // default is big endian

// x is now [0, 0, 0, 1, 0, 0 ...

y.setUint32(1, 2)

//    offset1 --+ (then 4 bytes over written with big endian 2)
//              |  |  |  |
//              V  V  V  V
// x is now [0, 0, 0, 0, 2, 0, ...  BYTES!!!

console.log(y.getUint32(0)) // 0 (incorrect)

// this gets the first 4 bytes as a big endian Uint32 so it's doing
//
// result = x[0] << 24 + x[1] << 16 + x[2] << 8 + x[3]
//          0    << 24 + 0    << 16 + 0    << 8 + 0
//          0

做最后一个例子

y.setUint32(0, 1)
y.setUint32(4, 2)

// x is now [0, 0, 0, 1, 0, 0, 0, 2, ....]

console.log(y.getUint32(0)) // 1

// this is reading the bytes 0 to 3
// x[0] << 24 + x[1] << 16 + x[2] << 8 + x[3]  
// 0    << 24 + 0    << 16 + 0    << 8 + 1    = 1 

console.log(y.getUint32(1)) // 256

// this is reading the bytes 1 to 4
// x[1] << 24 + x[2] << 16 + x[3] << 8 + x[4]  
// 0    << 24 + 0    << 16 + 1    << 8 + 0    = 256

console.log(y.getUint32(2)) // 65536

// this is reading the bytes 2 to 5
// x[2] << 24 + x[3] << 16 + x[4] << 8 + x[5]  
// 0    << 24 + 1    << 16 + 0    << 8 + 0    = 65536

console.log(y.getUint32(3)) // 16777216

// this is reading the bytes 3 to 6
// x[3] << 24 + x[4] << 16 + x[5] << 8 + x[6]  
// 1    << 24 + 0    << 16 + 0    << 8 + 0    = 16777216

console.log(y.getUint32(4)) // 2

// this is reading the bytes 4 to 7
// x[4] << 24 + x[5] << 16 + x[6] << 8 + x[7]  
// 0    << 24 + 0    << 16 + 0    << 8 + 2    = 2

对其余部分应用相同的逻辑,希望您的 DataView 问题得到解释?

你说你想通过引用 MDN 图并显示大量 0 和 1 将它们打印为字节,你是说位吗?你可以像这样打印一个数字

const v = 0x3A;
console.log(v.toString(2));

或填充到8位

const v = 0x3A;
console.log(v.toString(2).padStart(8, '0'));

要打印整个缓冲区,假设您有一个 Uint8Array 视图(如果没有)

const v = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF, 0x21]);
console.log(asBits(v));

function asBits(a) {
  const nums = [];
  a.forEach(v => nums.push(v.toString(2).padStart(8, '0')));
  return nums.join('');
}