在Java中每个对象都有一个hash值。无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码。
一.用处
在Object中通过hashCode方法来提供hash值子类可以重写此方法。以提供通常容器数据结构比如HashMap中的等同对象判断,解决hash表冲突问题。
java/lang/Object.java
public class Object {
public native int hashCode();
}
put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
java/util/HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//新增hash表或扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//hash表插入新节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode) //放到树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { //新增节点
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // 已存在于表中,更新value就行
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
}
二.实现
synchronized原理一篇中,我们知道对象的hashCode是存于对象头中,hashCode必然与对象头有关
src/share/vm/prims/jvm.cpp
JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
JVMWrapper("JVM_IHashCode");
return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
JVM_END
由于对象处于多线程环境中,需要判断对象是否处于同步状态,同步状态和非同步状态下对象头的状态有差异,例如重量级同步状态下,只需要从锁监视器哪里取回被锁定对象的对象头,这个对象头包含了一个hash码。
src/share/vm/runtime/synchronizer.cpp
intptr_t ObjectSynchronizer::FastHashCode(Thread * Self, oop obj) {
if (UseBiasedLocking) { //使用了偏向锁
if (obj->mark()->has_bias_pattern()) { //可偏向
// Handle for oop obj in case of STW safepoint
Handle hobj(Self, obj);
//撤销或重偏向
BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
obj = hobj();
}
}
ObjectMonitor* monitor = NULL;
markOop temp, test;
intptr_t hash;
//读取稳定的对象头
markOop mark = ReadStableMark(obj);
if (mark->is_neutral()) { // 未持有锁
hash = mark->hash(); //取对象头中的hash码
if (hash) {
return hash;
}
//重新生成一个
hash = get_next_hash(Self, obj); // allocate a new hash code
temp = mark->copy_set_hash(hash);
//CAS设置
test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
if (test == mark) {
return hash;
}
} else if (mark->has_monitor()) { //当前是重量级锁
monitor = mark->monitor();
temp = monitor->header(); //从锁中拿到对象头(加锁时锁会保存对象头)
hash = temp->hash(); //取得hash码
if (hash) {
return hash;
}
// Skip to the following code to reduce code size
} else if (Self->is_lock_owned((address)mark->locker())) { //对象锁是当前线程的锁
temp = mark->displaced_mark_helper();
hash = temp->hash(); //取hash码
if (hash) {
return hash;
}
}
//走到这里说明对象锁为轻量级锁或偏向锁,进行锁膨胀
monitor = ObjectSynchronizer::inflate(Self, obj, inflate_cause_hash_code);
//从监视器锁中拿到对象头,拿到hash码
mark = monitor->header();
hash = mark->hash();
if (hash == 0) {
//重新生成一个
hash = get_next_hash(Self, obj);
//重设
temp = mark->copy_set_hash(hash);
test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
if (test != mark) {
hash = test->hash();
}
}
// We finally get the hash
return hash;
}
生成hash码,根据hashCode的值生成方式有所不同有自增序列,随机数,内存地址。
src/share/vm/runtime/synchronizer.cpp
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0;
if (hashCode == 0) {
//由具体的操作系统生成的随机数
value = os::random();
} else if (hashCode == 1) {
//基于对象地址生成hash码
intptr_t addrBits = cast_from_oop<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 = cast_from_oop<intptr_t>(obj); //对象地址
} else {
//使用线程随机值计算
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;
return value;
}
三.equals()
由hashCode的生成方式可知hash码不具有唯一性,小概率存在重复。有些情况下在重写equals()方法时,应当去重写hashCode方法。只要hashCode不一致,两个对象一定不是同一个。
public class Slave {
private String name;
private int age;
public Slave(String name, int age) {
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return this.name.equals(((Slave)obj).name) && this.age== ((Slave)obj).age;
}
}
重写equals方法
public class Master {
public static void main(String[] args) {
Slave p1 = new Slave("Slave", 007);
Slave p2 = new Slave("Slave", 007);
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
HashMap<Slave, String> hashMap = new HashMap<Slave, String>();
hashMap.put(p1, "HotSpot");
System.out.println(hashMap.get(p2));
}
}
原本期望返回"HotSpot",但返回的时null
public class Slave {
private String name;
private int age;
public Slave(String name, int age) {
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return name.hashCode()*37+age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return this.name.equals(((Slave)obj).name) && this.age== ((Slave)obj).age;
}
}
重写hashCode方法后,返回了"HotSpot"。为什么,不重写hashCode方法,HotSpot将会为你随机生成一个,那么HashMap从散列表中是不同的key,自然返回null。重写hashCode后,p1和p2hash码是一致的自然从散列表中拿到的key是同一个,返回了“HotSpot”。