优秀的编程知识分享平台

网站首页 > 技术文章 正文

hashCode使用指南_hashcode算法

nanyue 2025-10-02 04:34:59 技术文章 1 ℃

首先我们看下Object类的部分源码,可以得到如下结论:

  1. 它是Object对象的一个方法,调用的本地方法(不同JVM实现不同)
  2. 通过该方法可以利用哈希表的一些优点,例如HashMap(方法描述翻译过来的大致意思)
/**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.
     * ....
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();

那么如何使用这个方法呢,如何去利用哈希表的优点呢?

我们可以看下JDK的Integer类和String类是如何实现的:

Integer的hashCode和equals:

@Override
public int hashCode() {
        return Integer.hashCode(value);
}

public static int hashCode(int value) {
        return value;
}

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
}
  1. Integer.hashCode和equals方法一样,都是通过内部的int值来实现的
  2. 不同int值的Integer对象hashCode不同,equals返回false;相同int值的Integer对象hashCode相同,equals返回true。

所以,Integer的int值具有天然的防止哈希碰撞的属性,这也是前几篇文章我们用Integer举例的原因。


String的hashCode和equals

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 obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
}
  1. String.hashCode和equals方法一样,都是通过内部的char数组来实现的
  2. String.hashCode的公式可以理解为char[0]*31^(n-1) + char[1]*31^(n-2) + ... + char[n-1]

为什么要使用h = 31 * h + val[i];这样的公式来产生hashcode呢?

答:这是一个多项式哈希,目的是为了将char数组的位置信息带入到hashcode的产生逻辑中。如果我们写成h = h + val[i]的话,字符串"abc"和"cba"将产生相同的hashcode,这样就发生哈希冲突了。

为什么String要用31作为乘数呢?

JDK-4045622(链接请见文章尾部)里面提到,当时针对三个数据源(横轴)和几种比较知名的哈希方法(纵轴)进行了统计,P(31)是其中效率比较高的之一(数值越小效率越高),同时因为31在RISC机器上计算效率更高(2的指数倍-2,2的指数倍相当于左移操作)。

Webster's	Code Strings	URLs
			---------       ------------	----
Current Java Fn.	1.2509		1.2738	       13.2560
P(37)    [Java]		1.2508		1.2481		1.2454
P(65599) [Aho et al]	1.2490		1.2510		1.2450
P(31)	 [K+R]		1.2500		1.2488		1.2425
P(33)    [Torek]	1.2500		1.2500		1.2453
Vo's Fn			1.2487		1.2471		1.2462
WAIS Fn			1.2497		1.2519		1.2452
Weinberger's Fn(MatPak) 6.5169		7.2142	       30.6864
Weinberger's Fn(24)	1.3222		1.2791		1.9732
Weinberger's Fn(28)	1.2530		1.2506		1.2439


如何使用hashCode方法呢?

我们同样看下HashMap里面是如何使用的:

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        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;
          ...

这里首先比较hash值是否一样(p.hash == hash),然后判断对象是否相等((k = p.key) == key || (key != null && key.equals(k)))。

总结

  1. 如果我们想将java对象使用哈希表的相关优点(插入/查找的复杂度为常量),我们需要重写hashCode和equals方法。
  2. hashCode方法的逻辑也很重要,会影响到程序的性能。
  3. 如何写好hashCode方法可以参考现有的JDK类的实现,它们背后都是经过了大量的实践和理论论证的。


相关链接:

JDK-4045622 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622

其他相关文章:

HashMap怎么学(一)

HashMap怎么学(二)数组扩容&链表生成

HashMap怎么学(三)红黑树

最近发表
标签列表