Javascript 类型数组和 Endianness

我使用 WebGL 来呈现二进制编码的网格文件。二进制文件以 big-endian 格式写出(我可以通过在十六进制编辑器中打开该文件或使用 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/中找不到任何关于类型化数组的默认 endianness 的引用,所以我想知道这是怎么回事?在使用类型化数组读取时,我是否应该假设所有二进制数据都应该是 little-endian?

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

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

DataView 函数(如“ getInt32”)默认情况下读取 big-endian 值。

(注意: 我使用谷歌 Chrome 15和 Firefox 8进行了测试,它们的表现都是一样的)

41029 次浏览

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

之所以有点悲哀,是因为这意味着几乎没有人会去测试他们的代码是否能在大端硬件上工作,这会损害硬件的工作,而且事实上整个 web 平台都是围绕着代码在不同的实现和平台上统一工作而设计的,这一点被打破了。

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

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

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

仅供参考,你可以使用下面的 javascript 函数来确定机器的 endianness,然后你可以传递一个适当格式化的文件到客户端(你可以在服务器上存储文件的两个版本,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");
}

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

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

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

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

特别是:

类型化数组视图类型的操作与主机计算机的字节顺序一致。

DataView 类型对具有指定 endianness (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.

检查 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]);
}

工作原理:

  • 创建一个4字节的数组;
  • 一个32位视图包装该数组;
  • view[0] = 1将数组设置为保存32位值1;
  • 现在重要的部分来了: 如果 system 是 big endian,那么1被最右边的字节占据(little 位于最后) ; 如果它是 little endian,那么它是最左边的字节来存储它(little 位于最前面)。因此,如果机器是 big endian,那么对最左边的字节执行位 AND 操作将返回 false;
  • 函数最后通过将 !运算符应用于 &运算的结果来将其转换为布尔值,同时也对其进行反转,以便对于 big endian 返回 true。

一个很好的调整是将它转换成 生命,这样你只能运行一次检查,然后缓存它,然后你的应用程序可以检查它多次,因为它需要:

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
}

另一个快速检查 Endianness 的方法:

这里只是添加了我的2Cents,但是,我发现下面的方法很有用,特别是在静态存储在 Singleton 中并且可以跨类使用的情况下:

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

如果每个8Bits 的存储顺序与十六进制输入的顺序不同,那么它使用的是 little endian。然后存储结果,以供进一步参考。结果之所以准确,是因为数据存储在缓冲区中,根据 ECMA 脚本规范,与它存储在设备上的方式相同。

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

为了说明这一点:

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 的精神。

这应该在 little-endian 上返回 true,在 big-endian 上返回 false:

function runtimeIsLittleEndian(){
return (new Uint8Array(new Uint16Array([1]).buffer)[0] === 1);
}

因为 little-endian 集[0]到1和[1]到0,相反 big endian 集[0]到0和[1]到1... 我想?实际上并没有一个可用的 big-endian 系统来进行测试。