资讯专栏INFORMATION COLUMN

谈谈java中几种常见的散列算法及解决哈希碰撞的方式

沈建明 / 1883人阅读

摘要:接下来分析几个常见的实现方式。再哈希法再哈希法,就是出现冲突后采用其他的哈希函数计算,直到不再冲突为止。,其中为不同的哈希函数。

由表及里,循序渐进,请往下看。随手点赞是对作者最大的鼓励!^0^。

什么是哈希表

引用:严蔚敏 《数据结构(C语言版)》中的内容

哈希表就是 依据关键字可以根据一定的算法(哈希函数)映射到表中的特定位置 的思想建立的表。因此哈希表最大的特点就是可以根据f(K)函数得到其在数组中的索引。

接下来来看看Java中Object对hashCode()方法的说明,当然此方法和equals(Object obj)方法是相辅相成的。

Object类中的equals和hashCode方法(文章内源码均基于JDK8) equals方法官方文档:

public boolean equals(Object obj)

Indicates whether some other object is "equal to" this one.

The equals method implements an equivalence relation on non-null
object references:

· It is reflexive: for any non-null reference value x, x.equals(x) should return true.
· It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
· It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
· It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
· For any non-null reference value x, x.equals(null) should return false.

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

Parameters:
obj the reference object with which to compare.
Returns:
true if this object is the same as the obj argument; false otherwise.
See Also:
hashCode(),java.util.HashMap

在官方说明中,指明了equals方法具有自反性、对称性、传递性、一致性,同时也提醒在在继承Object的时候,如果要重写hashCode方法,通常都需要重写该方法,因为hashCode要求(下面也有提及):如果两个对象执行equals方法结果为true,则两对象的哈希码应该是相等的。

hashCode方法官方文档:

public native int hashCode();

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.

The general contract of hashCode is:

· Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
· If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
· It is not required that if two objects are unequal according to the java.lang.Object.equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.)
Returns:
a hash code value for this object.
See Also:
java.lang.Object.equals(java.lang.Object),java.lang.System.identityHashCode

该方法返回对象的经过处理后的内存地址,由于每个对象的内存地址都不一样,所以哈希码也不一样。此方法为native方法,取决于JVM的内部设计,一般是某种C地址的偏移。

文档中给出了三条规定:

在对象没有被修改的前提下,执行多次调用,该hashCode方法必须始终返回相同的整数。

如果两个对象执行equals方法结果为true,则分别调用hashCode方法产生的整数结果是相等的。

非必要要求:两个对象执行equals方法结果为false,则分别调用hashCode方法产生的整数结果是不相等的。

第三个要求虽然为非必需,但如果实现,则可以提高散列表的性能。

接下来分析几个常见的实现方式。

String的equals和hashCode方法

hashCode方法源码:

    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;
    }

该函数很简单,以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模,达到了目的——只要字符串的内容相同,返回的哈希码也相同。但是乘子31在此需要解释一下。选31作为乘子,是因为:

31是一个奇质数,如果选择一个偶数会在乘法运算中产生溢出,导致数值信息丢失,因为乘二相当于移位运算。选择质数的优势并不是特别的明显,但这是一个传统。

31可以被JVM优化:31 * i = (i << 5) - i。

equals方法源码:

    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方法包含了"==",双等号比较的是地址,存储地址相同,内容则相同。当地址不同的时候,先验证了比较对象是否为String,接着比较了两个字符串的长度,最后才循环比较每个字符是否相等。

Integer的equals和hashCode方法

hashCode方法源码:

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

equals方法源码:

     public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

由此可见,Integer哈希码就是Integer对象里所包含的那个整数的数值,且equals方法比较的也是两者的整数数值,即两个数值大小的Integer对象,计算出的哈希码是相等的。

最后,像int,char这样的基础类,它们不需要hashCode,如果需要存储时,将进行自动装箱操作,计算方法同Integer。

哈希碰撞(hash冲突)

在计算hash地址的过程中会出现对于不同的关键字出现相同的哈希地址的情况,即key1 ≠ key2,但是f(key1) = f(key2),这种情况就是Hash 冲突。具有相同关键字的key1和key2称之为同义词。
通过优化哈希函数可以减少这种冲突的情况(如:均衡哈希函数),但是在通用条件下,考虑到于表格的长度有限及关键值(数据)的无限,这种冲突是不可避免的,所以就需要处理冲突。

冲突处理

冲突处理分为以下四种方式:

开放地址

再哈希

链地址

建立公共溢出区

其中开放地址又分为:

线性探测再散列

二次探测再散列

伪随机探测再散列

下面谈谈几种方法的原理:

开放地址

开放地址法处理冲突的基本原则就是出现冲突后按照一定算法查找一个空位置存放。公式:

Hi为计算出的地址,H(key)为哈希函数,di为增量。其中di的三种获取方式既是上面提到的开放地址法的三种分类(线性探测再散列、二次探测再散列、伪随机探测再散列)。

线性探测再散列

,即依次向后查找。

二次探测再散列
,即依次向前后查找,增量为1、2、3的二次方。

伪随机探测再散列
伪随机,顾名思义就是随机产生一个增量位移。

再哈希法

再哈希法,就是出现冲突后采用其他的哈希函数计算,直到不再冲突为止。

,其中RHi为不同的哈希函数。

链地址法

链接地址法不同与前两种方法,他是在出现冲突的地方存储一个链表,所有的同义词记录都存在其中。形象点说就行像是在出现冲突的地方直接把后续的值摞上去。例如HashMap,如下图。

建立公共溢出区

建立公共溢出区的基本思想是:假设哈希函数的值域是[1,m-1],则设向量HashTable[0...m-1]为基本表,每个分量存放一个记录,另外设向量OverTable[0...v]为溢出表,所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70660.html

相关文章

  • 看动画学算法之:hashtable

    摘要:散列是一种算法通过散列函数,将大型可变长度数据集映射为固定长度的较小整数数据集。在讨论散列函数的实现之前,让我们讨论理想的情况完美的散列函数。对于标准二次探测冲突解决方法,当哈希表的时,插入可能失败。  目录 简介 散列表的关键概念 数组和散列表 数组的问题 hash的问题 线性探测 二次探测 双倍散列 分离链接 ...

    JessYanCoding 评论0 收藏0
  • #yyds干货盘点#看动画学算法之:hashtable

    简介java中和hash相关并且常用的有两个类hashTable和hashMap,两个类的底层存储都是数组,这个数组不是普通的数组,而是被称为散列表的东西。散列表是一种将键映射到值的数据结构。它用哈希函数来将键映射到小范围的指数(一般为[0..哈希表大小-1])。同时需要提供冲突和对冲突的解决方案。今天我们来学习一下散列表的特性和作用。文末有代码地址,欢迎下载。散列表的关键概念散列表中比较关键的三...

    番茄西红柿 评论0 收藏2637
  • 算法小专栏:散列表(一)

    摘要:级别标签算法散列表哈希表作者审校团队本篇将介绍散列表哈希表的相关基础知识。该数即为散列表数组的下标。因此,散列表的最优情况就是平均情况,时间复杂度为常数级。建议高于时,考虑散列表翻倍扩容优秀的散列函数。 级别: ★☆☆☆☆ 标签:「算法」「Hash」「散列表」「哈希表」 作者: MrLiuQ 审校: QiShare团队 本篇将介绍散列表(哈希表)的相关基础知识。 一、简介 散列表(Has...

    renweihub 评论0 收藏0
  • js数据结构和算法(五)字典和散列(hash)

    摘要:哈希表也是种数据结构,它可以提供快速的插入操作和查找操作。一个更好的散列函数为了避免碰撞,首先要确保散列表中用来存储数据的数组其大小是个质数,这和计算散列值时使用的取余运算有关。散列函数将学生里的数字相加,使用函数计算出散列值。 什么是字典结构? 字典是以键值对形式存储数据的数据结构,就像电话号码薄里的名字和电话号码那样的一一对应的关系。 javascript的Object类就是以...

    Hegel_Gu 评论0 收藏0

发表评论

0条评论

沈建明

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<