重写isEqual的最佳实践:和散列
你如何正确地重写isEqual:
在Objective-C中? “catch”似乎是,如果两个对象相等(由isEqual:
方法确定),它们必须具有相同的散列值。
Cocoa Fundamentals Guide的Introspection部分提供了一个关于如何覆盖isEqual:
的例子isEqual:
复制如下,名为MyWidget
的类:
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToWidget:other];
}
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
if (self == aWidget)
return YES;
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
if (![[self data] isEqualToData:[aWidget data]])
return NO;
return YES;
}
它检查指针是否相等,然后检查类是否相等,最后使用isEqualToWidget:
比较对象,它只检查name
和data
属性。 该示例没有显示的是如何重写hash
。
假设有其他属性不影响平等,比如age
。 不应该重写hash
方法,只有name
和data
会影响哈希? 如果是这样,你会怎么做? 只需添加name
和data
的哈希值? 例如:
- (NSUInteger)hash {
NSUInteger hash = 0;
hash += [[self name] hash];
hash += [[self data] hash];
return hash;
}
这足够吗? 有更好的技术吗? 如果你有像int
这样的基元呢? 将它们转换为NSNumber
以获得它们的散列? 或像NSRect
结构?
( Brain fart :最初写了“按位或”,它们和|=
一起|=
。
从...开始
NSUInteger prime = 31;
NSUInteger result = 1;
然后为你做的每一个原始
result = prime * result + var
对于64位,你可能也想换个xor。
result = prime * result + (int) (var ^ (var >>> 32));
对于你使用0的对象为零,否则使用它们的哈希码。
result = prime * result + [var hash];
对于布尔值,你使用两个不同的值
result = prime * result + (var)?1231:1237;
解释和归属
这不是tcurdt的工作,并且评论需要更多解释,所以我相信对归因的编辑是公平的。
该算法在“Effective Java”一书中得到了推广,相关章节目前可在线查找。 该书推广了该算法,该算法现在是许多Java应用程序(包括Eclipse)中的默认算法。 然而,它来自一个更老的实现,这个实现不同地归因于Dan Bernstein或Chris Torek。 这个较旧的算法最初是围绕Usenet浮现的,而某些归因则很困难。 例如,这个Apache代码中有一些有趣的评论(搜索他们的名字)引用了原始源代码。
底线是,这是一个非常古老的简单哈希算法。 它不是最高性能的算法,它甚至不是数学上证明是“好”的算法。 但这很简单,很多人用了很长时间,效果很好,所以它有很多历史的支持。
我只是自己拾取Objective-C,所以我不能专门为那种语言说话,但在其他语言中,如果两个实例是“相等”,他们必须返回相同的散列 - 否则您将拥有全部尝试将它们用作散列表(或任何字典类型集合)中的键时存在各种各样的问题。
另一方面,如果两个实例不相同,它们可能会或可能不会具有相同的哈希值 - 最好不要。 这是在散列表上进行O(1)搜索和O(N)搜索之间的区别 - 如果所有散列相撞,则可能发现搜索表并不比搜索列表更好。
就最佳实践而言,您的哈希应该为其输入返回值的随机分布。 这意味着,例如,如果您有双精度值,但大多数值趋于在0到100之间进行聚类,则需要确保这些值返回的散列均匀分布在整个可能的散列值范围内。 这将显着提高你的表现。
这里有许多哈希算法,包括这里列出的几个哈希算法。 我尽量避免创建新的散列算法,因为它可能会有很大的性能影响,所以使用现有的散列方法并按照您的示例进行某种按位组合,是避免它的好方法。
我发现这个线程非常有帮助,提供了我需要的所有东西来获得我的isEqual:
和一个捕获实现的hash
方法。 在isEqual:
测试对象实例变量时isEqual:
示例代码使用:
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
在我的单元测试中,当我知道对象是相同的时候,这个错误(即返回NO)没有错误。 原因是,其中一个NSString
实例变量为零,所以上面的语句是:
if (![nil isEqual: nil])
return NO;
并且由于零将回应任何方法,这是完全合法的,但是
[nil isEqual: nil]
返回nil,这是NO,所以当对象和正在测试的对象都有一个零对象时,它们将被认为是不相等的(即, isEqual:
将返回NO)。
这个简单的解决方法是将if语句更改为:
if ([self name] != [aWidget name] && ![(id)[self name] isEqual:[aWidget name]])
return NO;
这样,如果他们的地址是相同的,则跳过方法调用,无论它们是否都是零或者都指向同一个对象,但是如果其中任一个不是零,或者它们指向不同的对象,则适当地调用比较器。
我希望这可以帮助人们在几分钟内搔抓。
链接地址: http://www.djcxy.com/p/58481.html