剛才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拼接字符串。
