hashcode相關的文章網上不少了, 寫這個主要是按本身的思路進行記錄java
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的老師發明的使用位移和異或運算生成隨機數的方法, 因此在計算機上運算速度很是快(移位指令須要的機器週期更少).有興趣的能夠去深刻了解.app
hashCode是由散列方法得來的, 因此不一樣對象按hashCode方法計算的散列值, 是可能相同的.
相似於存在兩個不一樣串擁有相同的MD5值, 而且可能存在未知的其它串MD5值相同.dom
因此hashCode相同的對象, 並不必定是相等的, 須要經過equals方法比較.
若是兩個對象的equals爲true
, 那麼這兩個對象的HashCode必定相同.jvm
兩個對象的hashCode值相同, 對於其做爲hashmap
的key沒有影響, 即便映射到同一個槽中, 也能夠經過對比key自己來進行區分.ide
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
這個其實也很好理解, 簡單點思考可看做是兩個數字在比較, 那麼知足這些性質即是理所固然.oop
判斷對象相等須要重寫equals方法, 不然會調用Object
的equals
方法.ui
爲何重寫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散列碼思想的封裝實現.
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每次都是實時計算的, 雖然其是一個本地方法, 速度很是快, 若是有大量重複使用的場景, 能夠考慮像Integer內部緩存int值爲-128到127的對象同樣進行緩存.