hashcode相關的文章網上很多了, 寫這個主要是按自己的思路進行記錄
hashCode是什麼
Object中的hashCode實現是一個本地方法, 生成一個表徵當前物件例項的特徵值.
public native int hashCode();
具體的實現根據jvm的實現可能會不同. JDK1.8中實際計算hashcode的get_next_hash
函式的實現如下(src/share/vm/runtime/synchronizer.cpp)
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it`s possible for two threads to race and generate the same RNG.
// On MP system we`ll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia`s xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we`ll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
hashcode為4時是直接使用的記憶體地址, 但預設使用的是hashcode>=5的隨機演算法. 可以用JVM parameter -XX:hashCode來調整.
查了下, xor-shift scheme是弗羅裡達州立大學一位叫做George Marsaglia的老師發明的使用位移和異或運算生成隨機數的方法, 所以在計算機上運算速度非常快(移位指令需要的機器週期更少).有興趣的可以去深入瞭解.
hashCode和equals方法
hashCode是由雜湊方法得來的, 所以不同物件按hashCode方法計算的雜湊值, 是可能相同的.
類似於存在兩個不同串擁有相同的MD5值, 並且可能存在未知的其它串MD5值相同.
所以hashCode相同的物件, 並不一定是相等的, 需要通過equals方法比較.
如果兩個物件的equals為true
, 那麼這兩個物件的HashCode一定相同.
兩個物件的hashCode值相同, 對於其作為hashmap
的key沒有影響, 即使對映到同一個槽中, 也可以通過對比key本身來進行區分.
equals需要滿足數個性質:
自反性, 對稱性, 傳遞性, 一致性.
對稱性: 如果x.equals(y)返回是
true
,那麼y.equals(x)也應該返回是true
自反性: x.equals(x)返回是true
傳遞性: 如果x.equals(y)返回是true
,而且y.equals(z)返回是true
,那麼z.equals(x)也應該返回是true
一致性: 如果x.equals(y)返回是true
,只要x和y內容一直不變, 那麼x.equals(y)始終都返回都是true
這個其實也很好理解, 簡單點思考可看作是兩個數字在比較, 那麼滿足這些性質便是理所當然.
判斷物件相等需要重寫equals方法, 否則會呼叫Object
的equals
方法.
為什麼重寫equals
方法, 通常需要重寫hashCode
方法?
假設現在有一個類Apple
, 有兩個屬性color和weight. 比較兩個Apple
類的例項A和B是否相等(當然其中一個可能不是其例項), 實際等價於判斷兩者的兩個屬性是否都相等; 如果不重寫hashCode
方法, 當new一個新的Apple
例項C, 將其color和weight屬性設定成與B相同, 這就導致B.equals(C)時兩者的hashCode不一致, 會產生理解上的混淆.
重寫hashCode的經典方式是使用17和31雜湊碼的實現:
public class Apple {
private String color;
private int weight;
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Apple)) {
return false;
}
Apple apple = (Apple) o;
return apple.color.equals(color) &&
apple.weight == weight;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + color.hashCode();
result = 31 * result + weight;
return result;
}
}
除此之外, 可以使用java.util.Objects
去重寫equals
和hashCode
, 也可以使用Apache Commons Lang的LangEqualsBuilder
和HashCodeBuilder
方法. 這兩種方式也是對於17和31雜湊碼思想的封裝實現.
集合類的hashCode
AbstractSet中的實現, 對所有元素的hashCode對應的int值進行累加求和. 這樣的話, 兩個都包括”a”, “b”, “c”三個元素的HashSet
, 不論新增次序, 其hashCode是一樣的.
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
AbstractList中的實現, 使用了31讓結果更分散.
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
AbstractMap是遍歷將每個entry的hashCode
累加. 等等.
如果兩個集合物件的hashCode
相等, 完全無法說明這兩個物件相等, 但如果不等, 說明這兩個物件肯定是不等的. 可作為一個快速判斷不等的方案.
快取物件的hashCode
hashCode每次都是實時計算的, 雖然其是一個本地方法, 速度非常快, 如果有大量重複使用的場景, 可以考慮像Integer內部快取int值為-128到127的物件一樣進行快取.