【debug】Java中的map比较
【debug】Java中的map比较
题目链接:242. 有效的字母异位词
事情起因:(由于自己对Java了解不深、有表达错误的请指出,感谢!)

群友发了一张代码截图,觉得自己写的代码并没有错,但是不能通过测试用例。
他的算法我简单说一下,有点麻烦,但算法不是这篇博客的重点。
- 两个Map:
hashS
、hashT
分别用于存放字符串s
和t
中字母出现的频率(19行之前); - 遍历长的字符串对应的Map,假设是
hashS
,判断hashS
与hashT
相同的字母频率是否一样,如果不一样则return false
当时我是在外面,看到了这段代码,理解了他的想法之后好像真的没什么问题。
复现Bug与初步解决
回来之后代码放到idea里面跑了一下,发现这个测试用例长度为50000位,而且每个字母出现的频率是一样的

打上断点继续向下走,看看是哪一步出现了问题。

没想到第一次循环就出错了,hashT.get(c)
与hashS.get(c)
的值都是1913,但是地址不一样,一个是{Integer@531}
,一个是{Integer@587}
。看来这里的!=
判断的是地址而不是值,使用equal()
方法即可,如下:

深层探索
事情并没有真正的解决,既然群友能通过一些测试用例,说明了一些情况使用!=
是可以用的,那几个测试用例都是用了很短的字符串,我也看下是什么情况,s="anagram"
,t="nagaram"
,这是个正确的用例,debug的结果是这样

这可就是玄学了呀,短的数据不会出现问题,而长的会出现。
猜想一:与Map的大小有关
因为长的测试用例有26个键值对(每个字母均出现),而短的只出现了5个字母。我设置测试用例s = t = "abcdefghijklmnopqrstuvwxyz"
(以下简称字母表),让每个字母都出现一次。

还是正常的,猜想错误
猜想二:与测试用例的长度有关
接着我就多复制些字母表,出错误的是在50000的长度,我把字母表长度先放到3000多,
1 |
|

还是有错误,继续缩短长度,将扩增代码循环6次,减少一次再看看

这次竟然又不出错了,玄学!妥妥的玄学!
我换了种思路,这次不用字母表了,只是用一个变量看看会不会触发这个BUG,如果依然触发,每次长度增加1,那么很快就会得到到底在多长的时候会出现。25行打上断点,被执行前一定会卡住。


最后做一次验证,是不是Map中,只要有val超过128就会发生地址改变的情况。这次的测试用例使用随机字母
1 |
|

很好!当字母w出现了超过128次时候地址发生了变化。128这个数字相信大家一定很敏感,那么这个范围很可能是[-128, 127]。那么则需要看看底层源码是怎么说的,去看看Map.java
,搜索127
,并没有发现相关说明。

{Integer@***}
这里面经常变得数字就是星号部分,那么去Ieteger.java
里面看看有什么发现。


结论
结合这两段我悟了,果然有这么个范围,在初始化值的时候,[-128, 127]这个范围的数是在缓存里面,所以当Map
中的value
小于128时候读取的是缓存中的数,所以他们地址是一样的,也就是hashS.get(c)==hashT.get(c)
的原因。当超过这个范围,则会在新的地方存放一个值,尽管hashS.get(c)
和hashT.get(c)
均为128,但是使用==
或!=
判断的时候,判断的是地址,得到hashS.get(c)!=hashT.get(c)
。
所以一定要写equal()方法进行判断
所以一定要写equal()方法进行判断
所以一定要写equal()方法进行判断
hashS.get(c).equal(hashT.get(c))
,这样判断的才是值而不是地址