千万不要误用 java 中的 HashCode 方法

刚才debug追堆栈的时候发现一个很奇怪的问题

我用IE8和Google的浏览器访问同一个地址

Action的 scope="session" 也设置了

而且两个浏览器提交的参数map也是互相独立的

不过很奇怪的一个 两个Action对象的hashmap是不同的
但是它们的对象变量 paraterMap 的 哈希值 居然是一个

我大不解!
( 找到原因以后发现这个问题其实只是一个好多年没有再提起的基础问题 )
不过发现确实有朋友在网上说这个问题曾经引发过bug



Java中的hashCode()方法是一个比较特殊的地方
Object类中的hashCode是一个本地方法
public native int hashCode();
即使查看源码你也找不到它的实现, 因为它不是一个java编写的方法
此方法比较两个对象的内存地址是不是相等

6.1 覆写的HashCode方法
很多API中的子类会覆盖这个方法,最典型的应该是String类, 在String类中
   /**
     * Returns a hash code for this string. The hash code for a
     * <code>String</code> object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using <code>int</code> arithmetic, where <code>s[i]</code> is the
     * <i>i</i>th character of the string, <code>n</code> is the length of
     * the string, and <code>^</code> indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            int off = offset;
            char val[] = value;
            int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
}
可以看到, String类是使用它的 value值作为参数然后进行运算得出hashcode的
换句话说, 只要值相同的String不管是不是一个对象,hash值全部相等

也就是说运行下面的代码你会看见完全相同的hashcode
至于在不同的两台电脑上会不会相同我不知道,因为参与运算的off偏移量可能会不同

6.2 小心使用HashMap对象的hashcode
这是一个极为危险的行为!

public final int hashCode() {
            return (key==null ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

在HashMap类中, 如果HashMap对象的内部对象是空的, 则hashcode 一定是 0
如果非空, 则遍历容器中的全部对象
然后取key的hashcode和value的hashcode 按位异或运算,然后把他们依次相加

也就是说, 如果你的两个HashMap的key和value全部相同的话, 那么, 它们的hashcode就是相同的

                HashMap map1 = new HashMap();
                map1.put("test", "testValue");
                map1.put("test1", "testValue1");
                map1.put("test2", "testValue2");
                
                HashMap map2 = new HashMap();
                map2.put("test", "testValue");
                map2.put("test1", "testValue1");
                map2.put("test2", "testValue2");
                
                System.out.println(map1.hashCode());
                System.out.println(map2.hashCode());

下面代码的输出值是相等的

所以! 在任何时候! 不要使用HashMap或一些特殊容器的Hashcode作为key来进行缓存




以下内容转自
http://dingjob.javaeye.com/blog/696768
http://dingjob.javaeye.com/blog/696768
之所以写了两个连接,是因为douban上那个添加连接会携带 a href 锚点标签
我就是想看看如果我不用这个它会不会自动判断是链接地址,然后自己给加上
很满意, 豆瓣做的不脑残╮(╯_╰)╭

2010-06-22
Map的HashCode做缓存key值引发的重大bug
文章分类:Java编程
现象:

计费和账户的交互通过Map来交互,基本数据格式如下{"pp900_88",20,"pp900_61",2……}

在不同的取值情况下,较多数据返回了相同的价格结果,导致计算价格错误。

 

应用场景:

产品计算价格时,使用cache缓存了价格结果数据,cache的key值是传入map的hashCode,本意是要实现完全相同的Map传入值从缓存取数据,减少数据库的访问。

 

 

原因分析:

 

一。通过如下代码模拟线上应用:

Java代码
1.public static void main(String[] args) {
2. HashMap map = new HashMap();
3.
4. for(int i = 0 ; i < 100 ; i ++){
5.
6. HashMap m1 = new HashMap();
7.
8. m1.put("pp900_88", i);
9.
10. m1.put("pp900_59", 30);
11.
12. m1.put("pp900_62", 6);
13.
14. m1.put("pp900_63", 4);
15.
16. m1.put("pp900_60", "y");
17.
18. m1.put("pp900_61", i);
19.
20. int hs = m1.hashCode();
21.
22. map.put(hs, map.get(hs)+","+i);
23. // System.out.println(i+"==="+m1.hashCode());
24. }
25. System.out.println(map.size());
26. System.out.println(map);
27. }
  public static void main(String[] args) {
        HashMap map = new HashMap();

        for(int i = 0 ; i < 100 ; i ++){

            HashMap m1 = new HashMap();

            m1.put("pp900_88", i);

            m1.put("pp900_59", 30);

            m1.put("pp900_62", 6);

            m1.put("pp900_63", 4);

            m1.put("pp900_60", "y");

            m1.put("pp900_61", i);

            int hs = m1.hashCode();

            map.put(hs, map.get(hs)+","+i);
            // System.out.println(i+"==="+m1.hashCode());
        }
        System.out.println(map.size());
        System.out.println(map);
    } 结果如下:

 

Java代码
1.{-479160017=null,56,57,58,59,60,61,62,63,
2. -479160049=null,40,41,42,43,44,45,46,47,
3. -479160033=null,48,49,50,51,52,53,54,55,
4. -479160129=null,0,1,2,3,4,5,6,7,64,65,66,67,68,69,70,71,
5. -479160097=null,16,17,18,19,20,21,22,23,80,81,82,83,84,85,86,87,
6. -479160113=null,8,9,10,11,12,13,14,15,72,73,74,75,76,77,78,79,
7. -479160065=null,32,33,34,35,36,37,38,39,96,97,98,99,
8. -479160081=null,24,25,26,27,28,29,30,31,88,89,90,91,92,93,94,95}
{-479160017=null,56,57,58,59,60,61,62,63,
 -479160049=null,40,41,42,43,44,45,46,47,
 -479160033=null,48,49,50,51,52,53,54,55,
 -479160129=null,0,1,2,3,4,5,6,7,64,65,66,67,68,69,70,71,
 -479160097=null,16,17,18,19,20,21,22,23,80,81,82,83,84,85,86,87,
 -479160113=null,8,9,10,11,12,13,14,15,72,73,74,75,76,77,78,79,
 -479160065=null,32,33,34,35,36,37,38,39,96,97,98,99,
 -479160081=null,24,25,26,27,28,29,30,31,88,89,90,91,92,93,94,95}

可以看到,重复是很有规律的,连续7、8个数据都是重复的,当然在真实情况下重复概率不会这么高(因为Map的其他key-value值不太可能完全相同)

 

二。分析hashCode的产生

HashMap的hashCode根据key和value值来计算hashCode,最后将各个元素的hashCode值相加,即

Java代码
1.public final int hashCode() {
2. return (key==null ? 0 : key.hashCode()) ^
3. (value==null ? 0 : value.hashCode());
4. }
 public final int hashCode() {
            return (key==null ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

以 m1.put("pp900_88", 1); m1.put("pp900_61", 1);和

     m1.put("pp900_88", 2); m1.put("pp900_61", 2);为例子:得到的key-Value的hashCode值如下

 

key key的hashCode value的hashCode Map的hashCode(key^value)

  pp900_88 -79859962 1 -79859961

  pp900_61 -79860031 1 -79860032
  pp900_88 -79859962 2 -79859964
  pp900_61 -79860031 2 -79860029
 
显然根据Map的hash值算法, Map的hashCode相加:第一行+第二行=第三行+第四行,这样产生重复数据也在所难免了,因为hashCode本来就不保证不同的输入值不会产生相同的结果。JSL的约束是对于相同的对象,必须产生相同的hashCode。
结论和改进措施:
1.不建议对结果进行缓存,结果缓存会带来很多问题,比如哪些数据变更需要刷新哪些缓存,缓存最好对原始纪录值进行缓存。
2.key值不采用hashCode算法,直接改为使用各个key-value的String拼接字符串。

posted @ 2016-09-26 18:48  kabibo  阅读(2808)  评论(0编辑  收藏  举报