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的对象同样进行缓存.