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

Lan*_*ard 2 javascript bit-manipulation typed-arrays arraybuffer

我见过的关于 ArrayBuffer 的唯一真正的教程来自HTML5Rocks。但我特别想知道如何操作单个字节。例如,Mozilla 的 ArrayBuffers 上的这个卡通显示了一个包裹在 Uint8Array 视图中的 ArrayBuffer 的图像:

在此处输入图片说明

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

var x = new ArrayBuffer(10)
x[0] = 1
x[1] = 0
...
x[9] = 1
Run Code Online (Sandbox Code Playgroud)

即手动设置字节。但是我还没有看到任何关于这种功能的文档。相反,您似乎应该使用 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
Run Code Online (Sandbox Code Playgroud)

但是,在使用 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)
Run Code Online (Sandbox Code Playgroud)

直到最后我得到与 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
Run Code Online (Sandbox Code Playgroud)

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

接下来,我希望能够将字节打印为 101011100100 等、整个 ArrayBuffer 或其中的一部分。

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

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

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

gma*_*man 9

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

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

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

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

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

const view = new Uint32Array(100);
Run Code Online (Sandbox Code Playgroud)

完全一样

const view = new Uint32Array(new ArrayBuffer(400));
Run Code Online (Sandbox Code Playgroud)

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

const buffer = new ArrayBuffer(400);
const view1 = new Uint32Array(buffer);
const view2 = new Uint8Array(buffer);
Run Code Online (Sandbox Code Playgroud)

与这两行相同

const view1 = new Uint32Array(100);
const view2 = new Uint8Array(view1.buffer);
Run Code Online (Sandbox Code Playgroud)

至于你的第一个例子,传递给的偏移量以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)
Run Code Online (Sandbox Code Playgroud)

第一个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
Run Code Online (Sandbox Code Playgroud)

做最后一个例子

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
Run Code Online (Sandbox Code Playgroud)

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

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

const view = new Uint32Array(100);
Run Code Online (Sandbox Code Playgroud)

或填充到 8 位

const view = new Uint32Array(new ArrayBuffer(400));
Run Code Online (Sandbox Code Playgroud)

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

const buffer = new ArrayBuffer(400);
const view1 = new Uint32Array(buffer);
const view2 = new Uint8Array(buffer);
Run Code Online (Sandbox Code Playgroud)