1.前言
最近在上下班的地鐵上把《阿里巴巴Java開發(fā)手冊》讀了一遍钾怔,感覺獲益匪淺。讀的過程中也有一些思考和疑惑奸忽,就抽時間親自嘗試了一下阎肝。下面用這篇文章把我的問題和答案整理出來甩骏。
2.正文
- 手冊里提到,如果重寫equals()方法先慷,就必須重寫hashCode()方法饮笛,網(wǎng)上查了一下如何重寫hashCode(),發(fā)現(xiàn)好多重寫方法里都出現(xiàn)了31這個神奇的數(shù)字论熙。以下是String類的hashCode()實現(xiàn):
@Override public int hashCode() {
int hash = hashCode;
if (hash == 0) {
if (count == 0) {
return 0;
}
for (int i = 0; i < count; ++i) {
hash = 31 * hash + charAt(i);
}
hashCode = hash;
}
return hash;
}
這個magic number勾起了我的興趣福青,重寫hashCode()方法為什么會不約而同地用到31這個數(shù)字呢?
在網(wǎng)上查了一下脓诡,這個問題也沒有標準的答案无午,以下是我查找到的一些比較令人信服的答案:
- 每個對象根據(jù)值計算HashCode,這個code大小雖然不奢求必須唯一(因為這樣通常計算會非常慢)祝谚,但是要盡可能的不要重復,因此基數(shù)要盡量的大且計算出來的值不要溢出宪迟,計算出來的hash地址越大,所謂的“沖突”就越少交惯,查找起來效率也會提高次泽。
- 素數(shù)的特性能夠使得它和其他數(shù)相乘后得到的結果比其他方式更容易產(chǎn)成唯一性,也就是hashcode值的沖突概率最小席爽。
- 31*N可以被編譯器優(yōu)化為左移5位后減N意荤,有較高的性能。
比較權威的答案是Stack OverFlow上:
According to Joshua Bloch's『Effective Java』:
The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting.
The advantage of using a prime is less clear, but it is traditional.
A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i.
Modern VMs do this sort of optimization automatically.
翻譯:
根據(jù) Joshua Bloch 的著作『Effective Java』:
設計者選擇 31 這個值是因為它是一個奇質數(shù)拳昌。如果它是一個偶數(shù)袭异,在使用乘法當中產(chǎn)生數(shù)值溢出時,原有數(shù)字的信息將會丟失炬藤,因為乘以二相當于位移御铃。
選擇質數(shù)的優(yōu)勢不是那么清晰,但是這是一個傳統(tǒng)沈矿。
31 的一個優(yōu)良的性質是:乘法可以被位移和減法替代: 31 * i == (i << 5) - i
現(xiàn)代的 VM 可以自行完成這個優(yōu)化上真。
- 手冊里還留下了一個問題:
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}
這段代碼執(zhí)行時不會拋出異常,但是如果將equals前面的"1"換成"2"就會拋出ConcurrentModificationException羹膳,使用Iterator來進行刪除操作也不會拋出異常睡互,這是為什么呢?
通過分析源碼發(fā)現(xiàn)陵像,在Iterator的remove()和next()方法中都調用了checkForComodification()方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
就是在這里拋出了ConcurrentModificationException.
modCount:修改次數(shù)
expectedModCount:預期的修改次數(shù)
使用ArrayList的remove()就珠,只會使modCount++,不會修改expectedModCount
使用Iterator的remove()醒颖,則是expectedModCount = ++modCount
前面提到remove("1")時不會拋出異常妻怎,這是因為foreach方法其實也是調用了Iterator的hasNext()和next(),next()里調了checkForComodification()泞歉,hasNext()卻沒調用逼侦,remove("1")后匿辩,hasNext()返回false,就不會走到next()榛丢,所以也就不會拋出異常了铲球,這其實只是一個巧合。
所以:不要在foreach循環(huán)里進行元素的remove/add操作晰赞,remove元素請使用Iterator方式稼病,如果并發(fā)操作,需要對Iterator對象加鎖掖鱼。
具體的分析過程可以參考:http://blog.csdn.net/qq_28816195/article/details/78433544
3.結語
參加工作后才發(fā)現(xiàn)溯饵,代碼規(guī)范其實是極其重要的,優(yōu)雅的代碼便于理解和維護锨用,更加節(jié)省時間丰刊。就像通過一個人的字就能大概看出這是一個什么樣的人,代碼亦是如此增拥。剛畢業(yè)不久的我啄巧,更應該培養(yǎng)好的代碼規(guī)范,這對今后的成長必然是大有益處的掌栅。