Ruby扩展类属性后,如何确保哈希中不存入重复对象?
关于Ruby自定义类Hash键的hash方法问题解答
1. 必须修改hash方法吗?
是的,必须修改,否则没法保证Hash中不会存入逻辑重复的对象。
Ruby里判断两个对象能不能作为Hash的同一个键,靠的是eql?和hash这对方法,两者逻辑必须一致:
- 如果两个对象用
eql?判断为相等,它们的hash值必须完全相同; - 反之,
hash值不同的对象,一定不会被Hash视为同一个键。
你给Book加了year属性后,假设你已经更新了eql?(或者重写了==方法,因为默认eql?会调用==)来包含year的判断,那hash方法必须同步把year的哈希值加进去。不然会出现两种问题:
- 两个逻辑上不同的Book(比如title、author相同但year不同)可能生成相同的hash值,导致Hash的查找逻辑混乱;
- 即使当前小数据集没出问题,随着数据量增大,逻辑上相等的Book(三个属性都相同)可能因为hash值没包含year,出现哈希碰撞或者Hash无法识别它们是同一个键,最终存入重复对象。
修改方式很直接,把year的哈希值加入组合逻辑就行:
原代码可能是:
def hash title.hash ^ author.hash end
修改后可以用XOR继续组合:
def hash title.hash ^ author.hash ^ year.hash end
更推荐用数组哈希的方式,逻辑更直观还能避免XOR的顺序问题:
def hash [title, author, year].hash end
2. hash方法中^的作用
^是Ruby的按位异或运算符,原代码里用它把多个属性的哈希值合并成一个单一整数(因为每个对象的hash方法返回的都是整数)。
按位异或的规则是:二进制位相同则结果为0,不同则为1。用它组合哈希值的原因是计算快、实现简单,但它有个明显的缺陷:异或是可交换的——a.hash ^ b.hash和b.hash ^ a.hash结果完全一样。这就导致如果两个Book对象的title和author互换,它们的hash值会相同,大幅增加哈希碰撞的概率。而用数组的hash方法(比如[title, author].hash)会考虑元素顺序,就不会有这个问题。
内容的提问来源于stack exchange,提问作者Leahcim
相关产品推荐
相关产品推荐

