javascript - 将类型数组存储在 ArrayBuffer 中

标签 javascript endianness arraybuffer typed-arrays

我有一个由 Uint8 组成的数据 ArrayBuffer。每个都是网络字节顺序(big-endian)。我的目标是将每个 Uint8 转换为小端顺序,然后将它们放回 ArrayBuffer 中。

我知道我可以使用类型化数组轻松分隔各个 Uint,如下所示:

var uintArr = new Uint8Array(ArrayBuffer);

从那里我可以交换每个项目的字节顺序,并有一个小字节序 Uint8 数组。

我不知道如何将该数组返回到 ArrayBuffer 中。

有没有办法在 Javascript 中做到这一点?

最佳答案

ArrayBuffer 是所有 View 的通用字节数组。类型化数组只是意味着数组上有一个关联的类型,例如 uint8、int16 等。所有 Uin8Array、Int32Array 等都是 ArrayBuffer 之上的 View ,以支持读取和写入它们所代表的类型。

(U)int8 数组没有字节顺序,因为它们是单字节(即没有要重新排序的)。字节需要表示更宽的内容,例如 (u)int-16 或 -32(直到 ES7 才支持 64 位整数,但您确实有 32 位和 64 位 IEEE float )。

您通过 View 读取或写入的任何内容最终都会返回到它们指向的同一个 ArrayBuffer 中 - 您甚至可以为同一个缓冲区设置多个 View 。

要交换 16 位顺序,您可以使用 Uint16Array View 简单地读取 ArrayBuffer 并手动交换:

var buffer16 = new Uint16Array(buffer); // use same buffer as for the Uin8Array view

for(var i = 0, v; i < buffer16.length; i++) {
  v = buffer16[i];
  buffer16[i] = ((v & 0xff) << 8) | ((v & 0xff00) >>> 8);  // mask and shift the two bytes
}

如果您有相同缓冲区的 8 位 View ,您现在可以使用新顺序读取单个字节。

对于 32 位你会这样做:

var buffer32 = new Uint32Array(buffer);

for(var i = 0, v; i < buffer32.length; i++) {
  v = buffer32[i];
  buffer32[i] = ((v & 0xff) << 24) |       // mask, move byte 0 to 3
                ((v & 0xff00) << 8) |      // mask, move byte 1 to 2
                ((v & 0xff0000) >>> 8) |   // mask, move byte 2 to 1 unsigned
                ((v & 0xff000000) >>> 24); // mask, move byte 3 to 0 unsigned
}

但是,这些 View 需要对齐的缓冲区,这意味着 (u)int16 的缓冲区长度必须对齐为偶数长度,而 32 位必须对齐为 4 个字节。

如果没有缓冲区,您可以改用 DataView 在任何位置读取和写入任何长度,但会牺牲一些性能:

var view = new DataView(buffer);
var alignedLength = ((buffer.length / 2)|0) * 2;  // aligned to 16-bit

// 16-bit
for(var i = 0, v; i < alignedLength; i += 2) {
  v = view.getUint16(i, false);  // read as big-endian
  view.setUint16(i, v, true);    // write as little-endian
}

对于 32 位,使用 getUint32/setUint32,将 i 递增 4。请注意,您必须“密切关注”最后一次读取,以防 ArrayBuffer 未对齐到 2 或 4字节(通常你会对齐 buffer.length 并在循环中使用它,如图所示,使用 4 表示 32 位)。

如果您的源缓冲区包含混合长度(例如,如果它包含原始二进制文件),您将必须根据文件格式规范解析每个值。为此,请使用 DataView。

例子

var buffer = new ArrayBuffer(4),
    b8 = new Uint8Array(buffer),
    b16 = new Uint16Array(buffer),
    b32 = new Uint32Array(buffer),
    view = new DataView(buffer);

setData();
show("Original unsigned big-endian");


// swap the value using 16-bit array
for(var i = 0, v; i < b16.length; i++) {
  v = b16[i];
  b16[i] = ((v & 0xff) << 8) | ((v & 0xff00) >>> 8);
}
show("Byte-order swapped 16-bits");

setData();
for(var i = 0, v; i < b32.length; i++) {
  v = b32[i];
  b32[i] = ((v & 0xff) << 24) |
           ((v & 0xff00) << 8) |
           ((v & 0xff0000) >>> 8) |
           ((v & 0xff000000) >>> 24);
}
show("Byte-order swapped 32-bits");

setData();
for(var i = 0, v; i < buffer.byteLength; i += 2) {
  v = view.getUint16(i, false);  // big-endian
  view.setUint16(i, v, true);    // little-endian
}
show("Byte-order swapped 16-bit using DataView");

setData();
for(var i = 0, v; i < buffer.byteLength; i += 4) {
  v = view.getUint32(i, false);  // big-endian
  view.setUint32(i, v, true);    // little-endian
}
show("Byte-order swapped 32-bit using DataView");


function valToHex(v) {
  return (v>>>0).toString(16)
}

function setData() {
  b8[0] = 255;   // "network" order / big.endian 0xff804020
  b8[1] = 128;
  b8[2] = 64;
  b8[3] = 32;
}

function show(pre) {
  document.write(pre + ": ");
  document.write("0x" + valToHex(b8[0]));
  document.write(valToHex(b8[1]));
  document.write(valToHex(b8[2]));
  document.write(valToHex(b8[3]) + "<br>");
}
body {font:16px monospace}

关于javascript - 将类型数组存储在 ArrayBuffer 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30676193/

相关文章:

ios - Swift Little endian 十六进制值到十进制

c++ - fread/fwrite 大小和计数

c++ - native 字节序和自动转换

javascript - 从通过 websocket 接收的 ArrayBuffer 读取字符串

javascript - 在 JS 中构建二进制缓冲区的干净方法是什么?

javascript - 如何在 Flowtype 中将返回值定义为 truthy?

javascript - 如果字符串长于给定参数,则在字符串末尾添加句点

javascript - 可滚动元素,如何在调整大小时更改偏移顶部?

javascript - 如何使用 javascript 将数组缓冲区保存到磁盘上的本地文件

javascript - if 条件 block 记录一条消息,但 else block 中仍在设置变量