hash函数是一套算法的统称。实际中的Hash函数是指把一个大范围映射到一个小范围。一般的说,hash函数可以简单的划分为如下几类:
1. 加法Hash;
2. 位运算Hash;
3. 乘法Hash;
4. 除法Hash;
5. 查表Hash;
6. 混合Hash;
显示接触到的,以除法Hash取余居多。
hashCode是 java.lang.Object.hashCode() 或者 java.lang.System.identityHashCode(obj) 会返回的值。它是一个对象的身份标识。官方叫法为:标识哈希码( identity hash code),作为hash函数的入参。
先看下jdk的源码:
// Object.java
public native int hashCode();
// System.java
public static native int identityHashCode(Object x);
从源码中我们可以看到,这两种方法都是本地方法native,其实最终的实现类似,我们以Object.hashcode()为例,看下jvm是如何实现的。
//openjdk\hotspot\src\share\vm\prims\jvm.cpp
// java.lang.Object ///
JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
JVMWrapper("JVM_IHashCode");
// as implemented in the classic virtual machine; return 0 if object is NULL
return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
JVM_END
我们继续看ObjectSynchronizer::FastHashCode的实现:
// hotspot\src\share\vm\runtime\synchronizer.cpp
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
... //忽略部分代码
// Inflate the monitor to set hash code
monitor = ObjectSynchronizer::inflate(Self, obj);
// Load displaced header and check it has hash code
mark = monitor->header();
assert (mark->is_neutral(), "invariant") ;
hash = mark->hash();
if (hash == 0) {
hash = get_next_hash(Self, obj);
temp = mark->copy_set_hash(hash); // merge hash code into header
assert (temp->is_neutral(), "invariant") ;
test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
if (test != mark) {
// The only update to the header in the monitor (outside GC)
// is install the hash code. If someone add new usage of
// displaced header, please update this code
hash = test->hash();
assert (test->is_neutral(), "invariant") ;
assert (hash != 0, "Trivial unexpected object/monitor header usage.");
}
}
// We finally get the hash
return hash;
}
从以上代码中可以看出,如何生成hashcode,jvm提供了基于某个hashCode 变量值的六种方法。怎么生成最终值取决于hashCode这个变量值。
0 - 使用Park-Miller伪随机数生成器(跟地址无关)
1 - 使用地址与一个随机数做异或(地址是输入因素的一部分)
2 - 总是返回常量1作为所有对象的identity hash code(跟地址无关)
3 - 使用全局的递增数列(跟地址无关)
4 - 使用对象地址的“当前”地址来作为它的identity hash code(就是当前地址)
5 - 使用线程局部状态来实现Marsaglia’s xor-shift随机数生成(跟地址无关)
我们从openjdk\hotspot\src\share\vm\runtime\globals.hpp 中可以看到jdk8的默认值为:
//openjdk\hotspot\src\share\vm\runtime\globals.hpp
product(intx, hashCode, 5, \
"(Unstable) select hashCode generation algorithm")
可以知道,hashcode的默认值跟地址无关。
打开源码看下该方法的实现
public boolean equals(Object obj) {
return (this == obj);
}
可以看到,Object的实现是,基于地址的比较。
关于HashMap,我们重点看下其get(key)方法的逻辑
// java.util.HashMap
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//Node数组不为空,数组长度大于0,数组对应下标的Node不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
//也是通过 hash & (length - 1) 来替代 hash % length 的
(first = tab[(n - 1) & hash]) != null) {
//先和第一个结点比,hash值相等且key不为空,key的第一个结点的key的对象地址和值均相等
//则返回第一个结点
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果key和第一个结点不匹配,则看.next是否为空,不为null则继续,为空则返回null
if ((e = first.next) != null) {
//如果此时是红黑树的结构,则进行处理getTreeNode()方法搜索key
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//是链表结构的话就一个一个遍历,直到找到key对应的结点,
//或者e的下一个结点为null退出循环
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
总结HashMap的key的判断逻辑,当 hash(key1.hashcode) == hash(key2.hashcode) 并且key1.equals(key2) 时,判定key是相等的。
java.lang.String类覆写了Object的hashcode和equals方法,我们简单看下其源码
// java.lang.String
// 依赖字符串的char值
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
// 比较的是字符是否完全一致。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
**==**设计的目的就是为比较两个对象是否是同一个对象。比较对象的相等不仅要比较对象内容相等,还要比较对象引用地址是否相等。
对于基本数据类型而言,比较就是判断这两个数值是否相等,(基本数据类型没有方法),不存在equals()和hashCode()比较的问题,下面的讨论都是针对引用数据类型展开的。
对于引用对象而言,比较两个引用变量的引用的是否是同一个对象,即比较的是两个引用地址是不是一样
我们以HashMap比较key值是否相同的场景为例,反证下hashcode和equal是否必须同时覆写,我们举例说明如下:
我们以Person(id,name等字段,当id相同时判定为相同)为例说明下为何hashcode和equal是必须同时覆写。
假如只覆写了equals方法,hashcode使用Object.hashCode(), Person(1)和Person(1)会在计算hashcode时,进行hash函数计算时,判定为不同的HashMap key,跟业务预期不符合。
只覆写hashcode方法时。Person(1)和Person(1)会有相同的hash函数结算结果,但是当比较equals时,因为默认比较的是地址,而二者的地址不同,会判定为不同的HashMap key,跟业务预期不符。