Javascript Typed Arrays和Endianness

Bob*_*Bob 58 javascript endianness webgl typed-arrays arraybuffer

我正在使用WebGL渲染二进制编码的网格文件.二进制文件以big-endian格式写出(我可以通过在十六进制编辑器中打开文件或使用fiddler查看网络流量来验证这一点).当我尝试使用Float32Array或Int32Array读取二进制响应时,二进制文件被解释为little-endian并且我的值是错误的:

// Interpret first 32bits in buffer as an int
var wrongValue = new Int32Array(binaryArrayBuffer)[0];
Run Code Online (Sandbox Code Playgroud)

我在http://www.khronos.org/registry/typedarray/specs/latest/找不到任何类型数组默认字节序的引用,所以我想知道这笔交易是什么?在使用类型化数组进行读取时,我是否应该假设所有二进制数据都应该是小端?

为了解决这个问题,我可以使用DataView对象(在上一个链接中讨论)并调用:

// Interpret first 32bits in buffer as an int
var correctValue = new DataView(binaryArrayBuffer).getInt32(0);
Run Code Online (Sandbox Code Playgroud)

默认情况下,"getInt32"等DataView函数会读取big-endian值.

(注意:我已经使用Google Chrome 15和Firefox 8进行了测试,它们的行为方式相同)

gsn*_*ers 62

可悲的是,当前的行为是字节序是底层硬件的字节序.由于几乎所有台式电脑都是x86,这意味着小端.大多数ARM操作系统使用小端模式(ARM处理器是双端的,因此可以在其中运行).

这有点令人难过的原因在于,这意味着几乎没有人会测试他们的代码是否适用于大端硬件,损害了什么,以及整个Web平台是围绕代码在整个实现和平台上统一工作而设计的,这打破了.

  • 不知怎的,我认为情况就是这样. (7认同)
  • 一点也不不幸。类型数组遵循平台的字节序,因为我们使用它们与本机API互操作,原生API以平台的字节序工作。如果类型数组具有固定的字节序,那么我们将失去使用它们的大量好处(在与所选字节序不匹配的平台上)。对于诸如OP之类的情况,其中涉及一个文件(或用于与定义特定字节序的各种协议进行交互,例如TCP等),这就是DataView的目的。 (3认同)
  • @TJCrowder 机器字节序肯定有用途,但更大的问题是我们在网络上看到的类型化数组的大多数使用不需要担心底层机器字节序,并且如果您确实依赖机器字节序它很可能在大端系统上崩溃(考虑到几乎没有人会在其中测试他们的 JS)。(请注意,在我写上述内容时,我正在 Opera 工作,迄今为止,他们可能占据了大端系统上提供的大多数浏览器。) (2认同)

Rya*_*yan 30

仅供参考,您可以使用以下javascript函数来确定机器的字节顺序,之后您可以将格式正确的文件传递给客户端(您可以在服务器,big endian和little endian上存储两个版本的文件):

function checkEndian() {
    var arrayBuffer = new ArrayBuffer(2);
    var uint8Array = new Uint8Array(arrayBuffer);
    var uint16array = new Uint16Array(arrayBuffer);
    uint8Array[0] = 0xAA; // set first byte
    uint8Array[1] = 0xBB; // set second byte
    if(uint16array[0] === 0xBBAA) return "little endian";
    if(uint16array[0] === 0xAABB) return "big endian";
    else throw new Error("Something crazy just happened");
}
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您可能必须以小端重新创建文件,或者运行整个数据结构以使其成为小端.使用上述方法的扭曲,你可以动态交换字节顺序(不是真的推荐,只有整个结构是相同的紧密包装类型才有意义,实际上你可以创建一个根据需要交换字节的存根函数):

function swapBytes(buf, size) {
    var bytes = new Uint8Array(buf);
    var len = bytes.length;
    var holder;

    if (size == 'WORD') {
        // 16 bit
        for (var i = 0; i<len; i+=2) {
            holder = bytes[i];
            bytes[i] = bytes[i+1];
            bytes[i+1] = holder;
        }
    } else if (size == 'DWORD') {
        // 32 bit
        for (var i = 0; i<len; i+=4) {
            holder = bytes[i];
            bytes[i] = bytes[i+3];
            bytes[i+3] = holder;
            holder = bytes[i+1];
            bytes[i+1] = bytes[i+2];
            bytes[i+2] = holder;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Chi*_*tor 27

从这里获取http://www.khronos.org/registry/typedarray/specs/latest/(当该规范完全实现时),您可以使用:

new DataView(binaryArrayBuffer).getInt32(0, true) // For little endian
new DataView(binaryArrayBuffer).getInt32(0, false) // For big endian
Run Code Online (Sandbox Code Playgroud)

但是,如果由于未实现这些方法而无法使用这些方法,则可以始终在标题上检查文件的魔术值(几乎每种格式都具有魔术值),以查看是否需要根据您的endiannes反转它.

此外,您可以在服务器上保存特定于endiannes的文件,并相应地将它们用于检测到的主机endiannes.


use*_*062 15

其他答案似乎有点过时,所以这里是最新规范的链接:

http://www.khronos.org/registry/typedarray/specs/latest/#2.1

特别是:

类型化数组视图类型使用主机的字节顺序进行操作.

DataView类型对具有指定字节序(big-endian或little-endian)的数据进行操作.

所以,如果你想读大端(网络字节顺序)/写数据,请参见: http://www.khronos.org/registry/typedarray/specs/latest/#DATAVIEW

// For multi-byte values, the optional littleEndian argument
// indicates whether a big-endian or little-endian value should be
// read. If false or undefined, a big-endian value is read.
Run Code Online (Sandbox Code Playgroud)

  • "如果为false或未定义,则读取big-endian值." - 只花了我几个小时或我的生命. (5认同)

Luc*_*iva 7

快速检查字节序的方法

/** @returns {Boolean} true if system is big endian */
function isBigEndian() {
    const array = new Uint8Array(4);
    const view = new Uint32Array(array.buffer);
    return !((view[0] = 1) & array[0]);
}
Run Code Online (Sandbox Code Playgroud)

怎么运行的:

  • 创建一个4字节的数组;
  • 32位视图包装该数组;
  • view[0] = 1 将数组设置为保留32位值1;
  • 现在是重要的部分:如果系统是大端字节,则1保持在最右边的字节(小数倒数);如果它是小尾数,则是存储它的最左字节(小写在前)。因此,如果机器是大端,则对最左边的字节进行按位与运算将返回false;否则,返回false。
  • 通过将!运算符应用于运算结果,该函数最终将其转换为布尔值&,同时还对其进行反转,以便对于大端字节序返回true。

一个不错的调整是将其转换为IIFE,这样您可以只运行一次检查然后将其缓存,然后您的应用程序可以根据需要多次检查它:

/** @returns {Boolean} true if system is big endian */
function isBigEndian() {
    const array = new Uint8Array(4);
    const view = new Uint32Array(array.buffer);
    return !((view[0] = 1) & array[0]);
}
Run Code Online (Sandbox Code Playgroud)