71

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

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

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

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

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

默认情况下,“getInt32”等 DataView 函数读取大端值。

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

4

5 回答 5

73

当前行为由底层硬件的字节序决定。由于几乎所有台式计算机都是 x86,这意味着小端。大多数 ARM 操作系统使用 little-endian 模式(ARM 处理器是双端的,因此可以在任何一种模式下运行)。

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

于 2011-10-23T23:57:39.677 回答
39

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

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");
}

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

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;
        }
    }
}
于 2013-10-26T11:14:56.963 回答
31

从这里获取 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

但是,如果您不能使用这些方法,因为它们没有实现,您可以随时检查文件头上的魔法值(几乎每种格式都有一个魔法值),看看是否需要根据您的字节序反转它。

此外,您可以在服务器上保存特定于字节序的文件,并根据检测到的主机字节序使用它们。

于 2011-10-24T13:22:01.720 回答
16

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

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

尤其是:

类型化数组视图类型以主机计算机的字节顺序运行。

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

因此,如果您想以 Big 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.
于 2014-07-03T12:05:49.153 回答
14

检查字节顺序的快速方法

/** @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]);
}

这个怎么运作:

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

一个不错的调整是把它变成一个IIFE,这样你可以只运行一次检查然后缓存它,然后你的应用程序可以根据需要多次检查它:

const isBigEndian = (() => {
    const array = new Uint8Array(4);
    const view = new Uint32Array(array.buffer);
    return !((view[0] = 1) & array[0]);
})();

// then in your application...
if (isBigEndian) {
    // do something
}
于 2018-10-16T02:17:04.530 回答