Node.js中的缓冲区比较

23

我是Node.js的新手。在这里没有Buffer比较,所以我应该使用像buffertools这样的模块来实现这些功能。

但当我在纯Node中比较Buffer对象时,我发现了一种相当奇怪的行为。

> var b1 = new Buffer([170]);
> var b2 = new Buffer([171]);
> b1
<Buffer aa>
> b2
<Buffer ab>
> b1 < b2
false
> b1 > b2
false
> b1 == b2
false

> var b1 = new Buffer([10]);
> var b2 = new Buffer([14]);
> b1
<Buffer 0a>
> b2
<Buffer 0e>
> b1 > b2
false
> b1 < b2
true
> b1 == b2
false

底层实际上发生了什么?


1
在这两种情况下,您如何声明 b1b2 - Zirak
你能提供b1和b2的定义吗? - fe_lix_
(1) 除非你知道自己在做什么,否则永远不要在JavaScript中使用==;相反,总是使用===。 (2) 在JavaScript中除了undefinednulltruefalse、数字(包括Infinity但排除NaN)和字符串外,没有等价测试;没有其他东西可以与===有意义的比较,因为这本质上是一个值身份运算符。 x === y表示xy共享相同的分配内存/是完全相同的对象。 否则x === y始终为false,因此例如[] === []false。—另请参见我对接受的答案的评论,如下。 - flow
2个回答

43

这就是对象比较运算符的工作原理:

var a = {}, b = {};
a === b; //false
a == b; //false
a > b; //false
a < b; //false

var c = { valueOf : function () { return 0; } };
var d = { valueOf : function () { return 1; } };
c === d; //false
c == d; //false
c > d; //false
c < d; //true

底层实现

有点类似

第一部分:相等性

这是最简单的部分。抽象相等(==规范)和严格相等(===规范)都检查是否引用了相同的对象(比较引用)。在这种情况下,它们显然不是同一个对象,因此答案是false==规范步骤10,===规范步骤7)。

因此,在两种情况下:

b1 == b2 //false
b1 === b2 //false

第二部分:比较开始

这是有趣的部分。让我们看一下关系运算符(<>)是如何被定义的。在这两种情况下,让我们跟随调用链。

x = b1 //<Buffer aa>
y = b2 //<Buffer ab>

//11.8.5 The Abstract Relational Comparison Algorithm (http://es5.github.com/#x11.8.5)
Let px be the result of calling ToPrimitive(x, hint Number).
Let py be the result of calling ToPrimitive(y, hint Number).

//9.1 ToPrimitive (http://es5.github.com/#x9.1)
InputType is Object, therefore we call the internal [[DefaultValue]] method with hint Number.

//8.12.8 [[DefaultValue]] (hint) http://es5.github.com/#x8.12.8
We try and fetch the object's toString method. If it's defined, call it.

最后我们达到了高潮:什么是一个缓冲区的 toString 方法?答案深藏在 node.js 内部。如果您想了解更多,请 查看。我们可以通过实验轻易地发现以下信息:

> b1.toString()
'�'
> b2.toString()
'�'

好的,那样并不是很有帮助。在抽象关系比较算法(一个非常花哨的名字,代表的实际上就是 <)中,你会发现有一步来处理字符串。它只是将字符串转换为它们的数字值 - 字符代码。让我们这样做:

> b1.toString().charCodeAt(0)
65533
> b2.toString().charCodeAt(0)
65533

65533是一个重要的数字。它是两个平方数的和:142^2 + 213^2。它还恰好是Unicode替换字符,表示“我不知道发生了什么”。这就是为什么它的十六进制等价物是FFF D。

显然,65533 === 65533,因此:

b1 < b2 //is
b1.toString().charCodeAt(0) < b2.toString().charCodeAt(0) //is
65533 < 65533 //false
b1 > b2 //following same logic as above, false

就是这样。

伙计,到底怎么回事?

好吧,我的解释可能不够清晰,导致你感到困惑。为了概括一下,这就是发生的事情:

  1. 你创建了一个缓冲区。 Benjamin Gruenbaum 帮我重现了你的测试用例,方法是执行以下代码:

    var b1 = new Buffer([170]), b2 = new Buffer([171]);

  2. 在输出到控制台时,这些值被转换成它们的十六进制等效值(参见Buffer#inspect):

    170..toString(16) === 'aa'

    171..toString(16) === 'ab'

  3. 然而,在内部,它们表示无效字符(因为这不是十六进制编码;再次强调,你可以深入实现细节,但我不会详细解释)。因此,当转换为字符串时,它们被表示为 Unicode 替换字符。

  4. 由于它们是不同的对象,任何等式运算符将返回 false

  5. 然而,由于小于和大于符号的工作方式,它们被转换为字符串(然后转换为数字)进行比较。根据第三点,在这种情况下它们是相同的值;因此,它们不能互相小于或大于,导致结果为 false

最后,只是为了让你开心:

b1 <= b2 //true
b1 >= b2 //true

2
有趣问题的精彩回答 :) - Benjamin Gruenbaum
你所谓的“抽象相等”(==)实际上应该被称为“虚假相等”或“意图良好但实现不佳的花哨等价性”;而你所谓的“严格相等”(===)既不是关于严格性也不是关于相等性,而是检查值的身份。在https://github.com/loveencounterflow/jseq上阅读这些问题的深入讨论。 - flow

10

已经有一个被接受的答案了,但是我觉得我也可以发表一下意见,因为我并不认为被接受的答案特别清晰或有帮助。甚至只是因为它回答了OP没有问的问题而是错误的。所以让我们梳理一下:

> var b1 = new Buffer([170]);
> var b2 = new Buffer([171]);
> b1 < b2
> b1 > b2
> b1 == b2

所需做的就是:“如何在缓冲区上执行等同性以及小于/大于比较(也称总排序)”。
答案为:
要么手动地遍历两个缓冲区的所有字节并对应比较每个字节,例如b1 [ idx ] === b2 [ idx ] ,要么使用Buffer.compare( b1, b2 ),它会返回-10+1中的一个值,具体取决于第一个缓冲区在排序时是否排在第二个缓冲区之前、与第二个缓况完全相同还是排在其后面(将包含缓冲区的列表d进行排序只需执行:d.sort( Buffer.compare ))。
请注意,我在第一个示例中使用了===;我的网站上关于JavaScript滥用==的评论应该明确说明了为什么这样做。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接