将HashMap存入自身出现异常行为,恳请技术人士解释原因
这个问题的核心在于Ruby中Hash的key是如何被定位的,以及Hash对象自身的hash值会随着内容变化而改变这一特性。咱们一步步拆解你的代码和现象:
先明确两个关键规则
Ruby的Hash在存储和查找键值对时,依赖两个核心方法:
hash方法:返回一个整数,用来确定键应该存在哪个“桶”里eql?方法:用来在同一个桶里精确匹配键
而Ruby的Hash对象本身,它的hash值是基于自身内容计算出来的——也就是说,当Hash的内容发生变化时,它的hash值会跟着改变。
逐行分析你的代码
hash = {}
此时这是一个空Hash,它的hash值是某个固定整数(比如假设是X),对象ID是唯一的。hash[hash] = hash
这一步做了两件关键的事:- 用当前Hash对象的
hash值X,把它作为key存入Hash中,对应的值也是它自己 - 但存入之后,Hash的内容从空变成了有一个键值对,它自身的
hash值会重新计算,变成了新的整数Y(和X完全不一样)
- 用当前Hash对象的
hash.keys.first == hash # true
这是符合预期的,因为keys数组里确实存着那个Hash对象本身——对象ID没变,==(本质调用eql?)自然返回true。hash[hash] # nil
问题就出在这:现在你用这个Hash对象去查找时,Ruby会先取它**当前的hash值Y**去定位桶,但这个键当初是存在X对应的桶里的,自然找不到,返回nil。hash.key?(hash) # False
和上面同理,key?方法也是先通过当前的hash值找桶,找不到对应的键,所以返回false。hash[hash.keys.first] # nil
虽然hash.keys.first是同一个对象,但此时它的hash值已经是Y了,查找时还是用Y去定位桶,和当初存储时的X不匹配,所以还是找不到。hash[{}] # nil
这个很好理解,{}是一个全新的空Hash对象,它的hash值和之前的X、Y都不一样,当然找不到任何键值对。
直观验证:打印hash值变化
你可以加几行代码亲眼看到变化:
hash = {} puts "空Hash的hash值: #{hash.hash}" hash[hash] = hash puts "存入自身后的hash值: #{hash.hash}"
运行后你会发现这两个hash值完全不同,这就是所有反直觉现象的根源。
总结
简单来说:当你把Hash作为key存入自身时,存入操作改变了Hash的内容,导致它的hash值发生了变化——而Hash查找键是基于当前的hash值,不是当初存储时的旧值,所以自然找不到已经存在的键了。
内容的提问来源于stack exchange,提问作者Philip M




