Javascript 类型数组和字节顺序

发布于 2024-12-11 15:37:13 字数 788 浏览 0 评论 0原文

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

// 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);

DataView 函数(例如“getInt32”)默认读取大端值。

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

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:

// 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?

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

// 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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

苦笑流年记忆 2024-12-18 15:37:13

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

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

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).

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.

绳情 2024-12-18 15:37:13

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

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):

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;
        }
    }
}
冷心人i 2024-12-18 15:37:13

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

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

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

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

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.

谁人与我共长歌 2024-12-18 15:37:13

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

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

特别是:

类型化数组视图类型按照主机的字节顺序进行操作。

DataView 类型对具有指定字节顺序(大字节序或小字节序)的数据进行操作。

因此,如果您想以 Big Endian(网络字节顺序)读/写数据,请参阅:
http://www.khronos.org/registry/typedarray/specs/latest/ #数据查看

// 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.

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

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).

So if you want to read/write data in Big Endian (Network Byte Order), see:
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.
蝶…霜飞 2024-12-18 15:37:13

检查字节顺序的快速方法

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

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] = 1 sets 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.

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:

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
}
紧拥背影 2024-12-18 15:37:13
function runtimeIsLittleEndian(){
    return (new Uint8Array(new Uint16Array([1]).buffer)[0] === 1);
}
function runtimeIsBigEndian(){
    return (new Uint8Array(new Uint16Array([1]).buffer)[0] === 0);
}

Little-endian 将 [0] 设置为 1,将 [1] 设置为 0;相反,大字节序将 [0] 设置为 0,将 [1] 设置为 1。

这也可以在不调用函数的情况下完成,因为字节序在运行时是恒定的:

const ENDIANNESS = ['BIG', 'LITTLE'][new Uint8Array(new Uint16Array([1]).buffer)[0]]
if (ENDIANNESS === 'BIG') {
    // do big-endian stuff
}
function runtimeIsLittleEndian(){
    return (new Uint8Array(new Uint16Array([1]).buffer)[0] === 1);
}
function runtimeIsBigEndian(){
    return (new Uint8Array(new Uint16Array([1]).buffer)[0] === 0);
}

Little-endian sets [0] to 1 and [1] to 0; conversely, big endian sets [0] to 0 and [1] to 1.

This can also be done without a function call, as endianness is constant at runtime:

const ENDIANNESS = ['BIG', 'LITTLE'][new Uint8Array(new Uint16Array([1]).buffer)[0]]
if (ENDIANNESS === 'BIG') {
    // do big-endian stuff
}
盗梦空间 2024-12-18 15:37:13

另一种检查字节顺序的快速方法:

只需在此处添加我的 2Cents,但是,我发现下面的首选方法很有用;特别是当静态存储在 Singleton 中并跨类可用时:

static isLittleEndian = (function(){
            var a8 = new Uint8Array(4);
            var a32 = new Uint32Array(a8.buffer)[0]=0xFFcc0011;
            return !(a8[0]===0xff);
        })();

如果每个 8Bit 的存储顺序与输入的十六进制不同,则它使用的是小端字节序。然后将结果存储起来,以供进一步参考。结果之所以准确,是因为根据 ECMA 脚本规范,数据存储在缓冲区中,与本地存储在设备上的方式相同。

事实上它只调用一次,然后存储它,这是非常有用的;特别是超过一百万次迭代,所有迭代都需要知道使用哪种字节序,包括最明显的一个,渲染。

为了说明这一点:

const isLittleEndian = (function(){
    console.log("isLittleEndian function called");
    var a8 = new Uint8Array(4);
    var a32 = new Uint32Array(a8.buffer)[0]=0xFFcc0011;
    return !(a8[0]===0xff);
})();

for(let i = 0; i!=5; i++){
  if(isLittleEndian){
    console.log("Little Endian");
  }else{
    console.log("Big Endian");
  }
}

它与已经发布的 isBigEndian 版本类似,只是反过来了;这符合 EndianNess 的精神。

Yet Another QUICK WAY to CHECK Endianness:

Just adding my 2Cents here, but, my preferred method below is something I’ve found useful; especially when stored statically in a Singleton and made available across classes:

static isLittleEndian = (function(){
            var a8 = new Uint8Array(4);
            var a32 = new Uint32Array(a8.buffer)[0]=0xFFcc0011;
            return !(a8[0]===0xff);
        })();

If each 8Bits is not stored in the same order as the hex was input, then it’s using little endian. The result is then stored, and is useful for further reference. The reason the result is accurate, is because data is stored in the buffer, the same way as it is stored Natively on the device, according to the ECMA script spec.

The fact it calls only once, and then stores it, is very useful; especially with a million+ Itterations, all needing to know which endianness to use, including the most obvious one, rendering.

To illustrate this:

const isLittleEndian = (function(){
    console.log("isLittleEndian function called");
    var a8 = new Uint8Array(4);
    var a32 = new Uint32Array(a8.buffer)[0]=0xFFcc0011;
    return !(a8[0]===0xff);
})();

for(let i = 0; i!=5; i++){
  if(isLittleEndian){
    console.log("Little Endian");
  }else{
    console.log("Big Endian");
  }
}

It’s similar to the isBigEndian version already posted, just done a the other way round; which is in the spirit of EndianNess.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文