【问题标题】:How to test for equality in ArrayBuffer, DataView, and TypedArray如何测试 ArrayBuffer、DataView 和 TypedArray 中的相等性
【发布时间】:2014-02-28 11:36:41
【问题描述】:

有没有办法测试两个 JavaScript ArrayBuffers 是否相等?我想为消息撰写方法编写测试。我发现的唯一方法是将 ArrayBuffer 转换为字符串,然后进行比较。我错过了什么吗?

下面的代码给出的是假的,即使我认为它应该是真的:

(function() {
    'use strict';

    /* Fill buffer with data of Verse header and user_auth
     * command */
    var buf_pos = 0;
    var name_len = 6
    var message_len = 4 + 1 + 1 + 1 + name_len + 1;

    var buf = new ArrayBuffer(message_len);
    var view = new DataView(buf);
    /* Verse header starts with version */
    view.setUint8(buf_pos, 1 << 4); /* First 4 bits are reserved for version of protocol */
    buf_pos += 2;
    /* The lenght of the message */
    view.setUint16(buf_pos, message_len);
    buf_pos += 2;

    buf_pos = 0;
    var buf2 = new ArrayBuffer(message_len);
    var view2 = new DataView(buf);
    /* Verse header starts with version */
    view2.setUint8(buf_pos, 1 << 4); /* First 4 bits are reserved for version of protocol */
    buf_pos += 2;
    /* The lenght of the message */
    view2.setUint16(buf_pos, message_len);
    buf_pos += 2;


    if(buf == buf2){
        console.log('true');
    }
    else{
        console.log('false');
    }


}());

如果我尝试比较 view 和 view2 它又是错误的。

【问题讨论】:

    标签: javascript arraybuffer typed-arrays


    【解决方案1】:

    您无法在 JavaScript 中使用 ===== 直接比较两个对象。
    这些运算符只会检查引用的相等性(即,如果表达式引用相同的对象)。

    但是,您可以使用 DataViewArrayView 对象来检索 ArrayBuffer 对象的特定部分的值并检查它们。

    如果你想检查标题:

    if (  view1.getUint8 (0) == view2.getUint8 (0)
       && view1.getUint16(2) == view2.getUint16(2)) ...
    

    或者如果您想检查缓冲区的全局性:

    function equal (buf1, buf2)
    {
        if (buf1.byteLength != buf2.byteLength) return false;
        var dv1 = new Int8Array(buf1);
        var dv2 = new Int8Array(buf2);
        for (var i = 0 ; i != buf1.byteLength ; i++)
        {
            if (dv1[i] != dv2[i]) return false;
        }
        return true;
    }
    

    如果您想实现基于ArrayBuffer 的复杂数据结构,我建议您创建自己的类,否则每次您想要移动时都必须求助于繁琐的原始DataView / ArrayView 实例一根火柴棒进出结构。

    【讨论】:

    • 谢谢。这正是我需要做的——实现一组通过 websocket 处理消息的类。我希望测试涵盖的方法和状态机。
    • 一个猜测......如果我们将两个数组都转换为字符串(比如 base 64)然后比较它们会起作用
    • 可以,但我怀疑这样会更有效率
    • 考虑使用DataView 以获得更好的性能。请参阅下面的答案。
    • @AnthumChris 为什么? Int8Array 也只是底层缓冲区的“视图”,它不会复制它。
    【解决方案2】:

    您始终可以将数组转换为字符串并进行比较。例如

    let a = new Uint8Array([1, 2, 3, 4]);
    let b = new Uint8Array([1, 2, 3, 4]);
    if (a.toString() == b.toString()) {
        console.log("Yes");
    } else {
        console.log("No");
    }
    

    【讨论】:

    • 如果其中一个 Buffer 两边都填充了 0,这可能不一定有效。如果你toString 一个 Buffer 填充了 0,则返回字符串的长度不会反映实际的字符串长度,字符串比较将不起作用。例如Buffer.from([80]).toString() 是“P”。 Buffer.from([80, 0]).toString() 也是“P”,但第二个的长度是 2。即使字符串显然只有一个字母。修剪字符串似乎也不起作用。
    【解决方案3】:

    在一般的 javascript 中,您目前必须比较两个 ArrayBuffer 对象,方法是用 TypedArray 包装每个对象,然后手动迭代每个元素并进行元素相等。

    如果底层缓冲区是 2 或 4 字节内存对齐的,那么您可以通过使用 Uint16 或 Uint32 类型数组进行比较来进行显着优化。

    /**
     * compare two binary arrays for equality
     * @param {(ArrayBuffer|ArrayBufferView)} a
     * @param {(ArrayBuffer|ArrayBufferView)} b 
     */
    function equal(a, b) {
      if (a instanceof ArrayBuffer) a = new Uint8Array(a, 0);
      if (b instanceof ArrayBuffer) b = new Uint8Array(b, 0);
      if (a.byteLength != b.byteLength) return false;
      if (aligned32(a) && aligned32(b))
        return equal32(a, b);
      if (aligned16(a) && aligned16(b))
        return equal16(a, b);
      return equal8(a, b);
    }
    
    function equal8(a, b) {
      const ua = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
      const ub = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
      return compare(ua, ub);
    }
    function equal16(a, b) {
      const ua = new Uint16Array(a.buffer, a.byteOffset, a.byteLength / 2);
      const ub = new Uint16Array(b.buffer, b.byteOffset, b.byteLength / 2);
      return compare(ua, ub);
    }
    function equal32(a, b) {
      const ua = new Uint32Array(a.buffer, a.byteOffset, a.byteLength / 4);
      const ub = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / 4);
      return compare(ua, ub);
    }
    
    function compare(a, b) {
      for (let i = a.length; -1 < i; i -= 1) {
        if ((a[i] !== b[i])) return false;
      }
      return true;
    }
    
    function aligned16(a) {
      return (a.byteOffset % 2 === 0) && (a.byteLength % 2 === 0);
    }
    
    function aligned32(a) {
      return (a.byteOffset % 4 === 0) && (a.byteLength % 4 === 0);
    }
    

    并通过以下方式调用:

    equal(buf1, buf2)
    

    here are the performance tests 用于 1、2、4 字节对齐的内存。

    替代方案:

    您还可以使用 WASM 获得更高的性能,但将数据传输到堆的成本可能会抵消比较优势。

    在 Node.JS 中,您可以使用 Buffer 获得更高的性能,因为它将具有本机代码:Buffer.from(buf1, 0).equals(Buffer.from(buf2, 0))

    【讨论】:

    • 此代码有两个错误: 1. 如果您传递的类型数组不是Int8ArrayUint8ArrayUint8ClampedArray 的实例,则equal 将无法正常工作,因为@987654334 @ 将返回元素单位的长度,而不是字节。你应该改用.byteLength
    • 2.不幸的是,equal64 再快也没用。您不能使用浮点数来比较任意缓冲区。它既有误报也有误报:equal64(new Uint8Array([0,0,0,0,0,0,0,0]), new Uint8Array([0,0,0,0,0,0,0,0x80])); // +0 and -0true equal64(new Uint8Array([0,0,0,0,0,0,0xF8,0xFF]), new Uint8Array([0,0,0,0,0,0,0xF8,0xFF])); // NaNfalse
    • @emk 我确实想知道 float64 点,感谢您的证明。谢谢,我会相应地更新和修复。
    • 请注意,BigUint64Array 现已发货。
    • 您还可以通过比较对齐的前缀来进一步优化测试(例如,给定 131 个字节,将前 131 个字节作为较长的字进行比较,然后一次比较最后三个字节一个字节)。
    【解决方案4】:

    在今天的 V8 中,DataView 现在应该“可用于性能关键的实际应用程序” — https://v8.dev/blog/dataview

    下面的函数根据您已经实例化的对象测试相等性。如果您已经有 TypedArray 对象,则可以直接比较它们,而无需为它们创建额外的 DataView 对象(欢迎有人来衡量这两个选项的性能)。

    // compare ArrayBuffers
    function arrayBuffersAreEqual(a, b) {
      return dataViewsAreEqual(new DataView(a), new DataView(b));
    }
    
    // compare DataViews
    function dataViewsAreEqual(a, b) {
      if (a.byteLength !== b.byteLength) return false;
      for (let i=0; i < a.byteLength; i++) {
        if (a.getUint8(i) !== b.getUint8(i)) return false;
      }
      return true;
    }
    
    // compare TypedArrays
    function typedArraysAreEqual(a, b) {
      if (a.byteLength !== b.byteLength) return false;
      return a.every((val, i) => val === b[i]);
    }
    

    【讨论】:

      【解决方案5】:

      要测试两个 TypedArray 之间的相等性,请考虑使用 every 方法,该方法会在发现不一致时立即退出:

      const a = Uint8Array.from([0,1,2,3]);
      const b = Uint8Array.from([0,1,2,3]);
      const c = Uint8Array.from([0,1,2,3,4]);
      const areEqual = (first, second) =>
          first.length === second.length && first.every((value, index) => value === second[index]);
      
      console.log(areEqual(a, b));
      console.log(areEqual(a, c));
      

      这比替代方案(如toString() 比较)更便宜,即使在发现差异后也会遍历剩余的数组。

      【讨论】:

      • 不错的解决方案,但需要进行长度检查。如果 'second' 比 'first' 长,并且第一个字节相等,这将不起作用。
      【解决方案6】:

      我编写了这些函数来比较最普通的数据类型。它适用于 ArrayBuffer、TypedArray、DataView、Node.js 缓冲区和任何带有字节数据 (0-255) 的普通数组。

      // It will not copy any underlying buffers, instead it will create a view into them.
      function dataToUint8Array(data) {
        let uint8array
        if (data instanceof ArrayBuffer || Array.isArray(data)) {
          uint8array = new Uint8Array(data)
        } else if (data instanceof Buffer) { // Node.js Buffer
          uint8array = new Uint8Array(data.buffer, data.byteOffset, data.length)
        } else if (ArrayBuffer.isView(data)) { // DataView, TypedArray or Node.js Buffer
          uint8array = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
        } else {
          throw Error('Data is not an ArrayBuffer, TypedArray, DataView or a Node.js Buffer.')
        }
        return uint8array
      }
      
      function compareData(a, b) {
        a = dataToUint8Array(a); b = dataToUint8Array(b)
        if (a.byteLength != b.byteLength) return false
        return a.every((val, i) => val == b[i])
      }
      

      【讨论】:

        猜你喜欢
        • 2021-09-24
        • 2010-11-28
        • 2019-01-28
        • 1970-01-01
        • 1970-01-01
        • 2014-06-25
        • 2014-11-20
        • 2011-06-11
        • 2011-12-24
        相关资源
        最近更新 更多