【问题标题】:How to use ArrayBuffers with DataViews in JavaScript如何在 JavaScript 中将 ArrayBuffers 与 DataViews 一起使用
【发布时间】:2018-12-29 08:20:38
【问题描述】:

我看到的关于 ArrayBuffer 的唯一真实教程来自 HTML5Rocks。但我特别想知道如何操作各个字节。例如,Mozilla ArrayBuffers 上的这个cartoon 显示了包裹在 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 和相关的 TypedArrays,我只是想学习如何使用较低级别的 ArrayBuffer。

【问题讨论】:

  • 好吧,on the MDN site 它声明“您不能直接操作 ArrayBuffer 的内容”。所以这就是为什么你不能做x[0] = 1之类的事情它还提到当你新建一个ArrayBuffer时,它的所有条目都初始化为0。这就是为什么例如console.log(y.getUint32(1)) // 0 (?)你的问题的其余部分很好,但是你'为一个问题提出了很多问题,使这个问题过于广泛......

标签: javascript bit-manipulation arraybuffer typed-arrays


【解决方案1】:

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('');
}

【讨论】:

  • 非常感谢,这很有帮助。我也很欣赏性能提示。
  • 很好的解释!
猜你喜欢
  • 2020-03-31
  • 2015-04-14
  • 1970-01-01
  • 2016-05-31
  • 1970-01-01
  • 2021-11-04
  • 2013-10-01
  • 2022-01-13
  • 1970-01-01
相关资源
最近更新 更多