Javascript 类型数组和字节序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7869752/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 03:54:48  来源:igfitidea点击:

Javascript Typed Arrays and Endianness

javascriptendiannesswebgltyped-arraysarraybuffer

提问by Bob

I'm using WebGL to render a binary encoded mesh file. The binary file is written out in big-endian format (I can verify this by opening the file in a hex editor, or viewing the network traffic using fiddler). When I try to read the binary response using a Float32Array or Int32Array, the binary is interpreted as little-endian and my values are wrong:

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

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

I can't find any references to the default endianness of typed arrays in http://www.khronos.org/registry/typedarray/specs/latest/so I'm wondering what's the deal? Should I assume that all binary data should be little-endian when reading using typed arrays?

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

To get around the problem I can use a DataView object (discussed in the previous link) and call:

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

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

The DataView functions such as "getInt32" read big-endian values by default.

默认情况下,诸如“getInt32”之类的 DataView 函数读取大端值。

(Note: I've tested using Google Chrome 15 and Firefox 8 and they both behave the same way)

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

回答by gsnedders

The current behaviour, is determined by the endianness of the underlying hardware. As almost all desktop computers are x86, this means little-endian. Most ARM OSes use little-endian mode (ARM processors are bi-endian and thus can operate in either).

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

The reason why this is somewhat sad is the fact that it means almost nobody will test whether their code works on big-endian hardware, hurting what does, and the fact that the entire web platform was designed around code working uniformly across implementations and platforms, which this breaks.

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

回答by Ryan

FYI you can use the following javascript function to determine the endianness of the machine, after which you can pass an appropriately formatted file to the client (you can store two versions of the file on server, big endian and little endian):

仅供参考,您可以使用以下 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");
}

In your case you will probably have to either recreate the file in little endian, or run through the entire data structure to make it little endian. Using a twist of the above method you can swap endianness on the fly (not really recommended and only makes sense if the entire structure is the same tightly packed types, in reality you can create a stub function that swaps bytes as needed):

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

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

回答by Chiguireitor

Taken from here http://www.khronos.org/registry/typedarray/specs/latest/(when that spec is fully implemented) you can use:

取自这里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

However, if you can't use those method because they aren't implemented, you can always check the file's magic value (almost every format has a magic value) on the header to see if you need to invert it according to your endiannes.

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

Also, you can save endiannes-specific files on your server and use them accordingly to the detected host endiannes.

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

回答by user1338062

The other answers seem a bit outdated to me, so here's a link to the latest spec:

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

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

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

In particular:

特别是:

The typed array view types operate with the endianness of the host computer.

The DataView type operates upon data with a specified endianness (big-endian or little-endian).

类型化数组视图类型以主机的字节序操作。

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

So if you want to read/write data in Big Endian (Network Byte Order), see: http://www.khronos.org/registry/typedarray/specs/latest/#DATAVIEW

因此,如果您想以 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.

回答by Lucio Paiva

Quick way to check endianness

检查字节序的快速方法

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

How it works:

这个怎么运作:

  • an array of 4 bytes is created;
  • a 32-bit view wraps that array;
  • view[0] = 1sets the array to hold 32-bit value 1;
  • now comes the important part: if system is big endian, that 1 is being hold by the rightmost byte (little comes last); if it is little endian, it is the leftmost byte that stores it (little comes first). So doing a bitwise AND with the leftmost byte returns false if the machine is big endian;
  • the function finally converts it to a boolean by applying the !operator to the result of the &operation, while also inverting it so that it returns true for big endian.
  • 创建了一个 4 个字节的数组;
  • 一个 32 位视图包装了该数组;
  • view[0] = 1设置数组以保存 32 位值 1;
  • 现在是重要的部分:如果系统是大端,则 1 由最右边的字节保存(小到最后);如果是小端,则存储它的是最左边的字节(小字节在前)。因此,如果机器是大端字节序的,那么对最左边的字节进行按位与运算会返回 false;
  • 该函数最终通过将!运算符应用于运算结果将其转换为布尔值&,同时还反转它以便它为大端返回 true。

One nice tweak is to turn it into an IIFE, that way you can run the check only once and then cache it, then your application can check it as many times as it needs:

一个不错的调整是将其转换为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
}