Java hashCode equals

本文详细介绍了哈希函数的基本概念,包括哈希表、哈希算法、哈希函数构造方法及其优缺点,并探讨了解决哈希冲突的策略。此外,还深入解析了Java中的Object.hashCode和Object.equals方法的工作原理及应用场景。

学习哈希函数,Java 中的 Object.hashCodeObject.equals 的相关内容


主要内容:

  1. 哈希函数
  2. Object.hashCodeObject.equals
  3. 重写 hashCodeequals 方法

哈希函数

参考:

数据结构 - 9.3 哈希表

哈希算法

哈希表

给定表 M,存在函数 f(key),对任意给定的关键字值 key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表 M 为哈希(Hash)表,函数 f(key) 为哈希(Hash)函数,得到的地址称之为 哈希地址 或者 散列地址

碰撞(collision)现象: 对于不同的关键字,经过哈希函数处理后可能得到同一个值

均匀哈希函数(uniform hash function:对于关键字集合中的任一个关键字,经哈希函数映像到地址集合中任何一个地址的概率是相等的。换句话说,均匀哈希函数能够使得一组关键字的哈希地址均匀的分布在整个地址区间,从而减少冲突。

好的哈希函数就像均匀哈希函数一样,能够最大可能的减少哈希地址冲突的情况,但是,仍旧会出现碰撞现象,所以,除了设计好的哈希函数外,还要有解决冲突的方法

哈希函数构造方法

常用哈希函数构造方法如下:

  • 直接定址法:取关键字或关键字的某个线性函数值为哈希地址。

    • 公式:H(key)=key 或者 H(key)=a*key+b,其中 ab 为常数
    • 优点:直接定址法得到的地址集合和关键字集合的大小相同,因此,对于不同的关键字不会发生冲突
  • 数字分析法:假设关键字是以 r 为基的数(比如,十进制数以 10 为基数),并且哈希表中可能出现的关键字都已事前得知,则可取关键字的若干数位组成哈希地址

    • 比如,关键字是 8 位十进制数,则可以取关键字的个位和百位数作为哈希地址,比如,两个位数的值相加。具体的实现应该分析关键字各个位数上的数值分布,尽量做到避免冲突
  • 平方取中法:取关键字平方后的中间几位为哈希地址

    • 通常在选定哈希函数时不一定能知道关键字的全部情况,取其中哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取得位数由表长决定
  • 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址

    • 使用范围:关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以使用折叠法得到哈希地址
  • 除留取余法:取关键字被某个不大于哈希表表长 m 的数 p 除后所得余数为哈希地址

    • 公式:H(key) = key MOD p,p <= m
    • 最简单,最常用的构造哈希函数的方法。不仅可以对关键字直接取模(MOD),也可在折叠,平方取中等运算之后取模

哈希函数的实现不仅仅只有上面几种,需要根据实际情况进行分析设计。需要考虑的因素如下:

  • 计算哈希函数所需时间(包括硬件指令的因素)
  • 关键字的长度
  • 哈希表的大小
  • 关键字的分布情况
  • 记录的查找频率

解决冲突方法

当出现了碰撞现象,要解决的是如何在哈希表上找到一个空的地址,以存储冲突的数据

常用解决冲突方法如下:

  1. 开放定址法

    1. 公式:Hi = (H(key) + Di) MOD m,i = 1,2,...,k(k <= m-1)。其中,H(key) 为哈希函数;m 为哈希表表长;Di 为增量序列
    2. 增量序列有 3 种取法:
      1. Di = 1,2,3,...,m-1,称为线性探测再散列
      2. Di = 1^2,-1^2,2^2,-2^2,...,k^2(k<=m/2),称为二次探测再散列
      3. Di = 伪随机数序列,称为伪随机探测再散列
  2. 再哈希法

    1. 公式:Hi = RHi(key),i=1,2,..,k。RH1, RH2, RH3... 均是不同的哈希函数,即产生地址冲突后计算另一个哈希函数地址,知道冲突不再发生
  3. 链地址法

    1. 将所有哈希地址相同的记录存储在同一个链表中
  4. 建立一个公共溢出区

    1. 原理:新建一个向量表,将发生冲突的关键字的记录均放入该向量表中

Object.hashCodeObject.equals

Object 中有两个函数 hashCodeequals,其都可以用于对象比对,下面学习它们之间的异同

Object.hashCode

public native int hashCode();

函数结果:返回一个哈希码(通常是将对象内存地址通过哈希算法转换为一个数字)

函数功能:

  • 在同一次运行过程中,同一个对象每次调用该方法得到的哈希码不变;但是,在多次运行过程中,得到的哈希码可能不一致
  • 如果两个对象通过 equals 方法比较是相同的,那么这两个对象的 哈希码 一定相同
  • 由于 碰撞现象 的存在,两个对象的 哈希码 相同时,其调用 equals 方法不一定返回 true;两个对象通过 equals 方法比较是不同时,也不代表两个对象的 哈希码 一定不相同

Object.equals

public boolean equals(Object obj) {
    return (this == obj);
}

函数结果:如果两个对象相同,返回 true;否则,返回 false

函数要求:调用 equals 方法的对象不能为空

函数功能:判断两个对象 是否引用了同一个地址。对象和 null 比较总是返回 false

测试代码:

/**
 * Created by zj on 2017/10/18.
 */
public class EqualsDemo {

    public static void main(String[] args) {
        EqualsDemo demo = new EqualsDemo();
        System.out.println(demo.hashCode());
        EqualsDemo demo1 = new EqualsDemo();
        System.out.println(demo1.hashCode());

        System.out.println(demo.equals(demo1));
        System.out.println(demo.equals(demo));
        System.out.println(demo.equals(null));
    }

}

这里写图片描述


重写 hashCodeequals 方法

比较运算符 ==

参考:疯狂Java讲义 - 3.7节

  • 如果比较的是数值,则判断这两个数值是否相等
  • 如果比较的操作数是引用类型,则判断两个操作数引用的是否是同一个对象

问题:为什么要重写 hashCode 方法和 equals 方法

由上面的分析可知,Object 类的 equals 方法比对的是两个对象的引用地址是否一致,但很多时候,我们需要比较的是 两个对象的值是否一致,所以需要重写 equals 方法。

与此同时,为了保持 equals 方法和 hashCode 方法的关系,所以也要重写 hashCode 方法

Integer / String / StringBufferhashCodeequals 方法

示例程序如下:

public static void main(String[] args) {
    Integer integer = 3;
    Integer integer1 = 3;

    System.out.println(String.format(Locale.CHINA, "%d %d", integer.hashCode(), integer1.hashCode()));
    System.out.println(integer.equals(integer1));

    String str = "Hello World";
    String str2 = "Hello World";

    System.out.println(String.format(Locale.CHINA, "%d %d", str.hashCode(), str2.hashCode()));
    System.out.println(str.equals(str2));

    StringBuffer sb = new StringBuffer("zj");
    StringBuffer sb2 = new StringBuffer("zj");
    System.out.println(String.format(Locale.CHINA, "%d %d", sb.hashCode(), sb2.hashCode()));
    System.out.println(sb.equals(sb2));
}

这里写图片描述

  • Integer 类是原始数据类型 int 的包装类,其重写了 equalshashCode 方法:

    /**
     * Returns a hash code for this {@code Integer}.
     *
     * @return  a hash code value for this object, equal to the
     *          primitive {@code int} value represented by this
     *          {@code Integer} object.
     */
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }
    
    /**
     * Compares this object to the specified object.  The result is
     * {@code true} if and only if the argument is not
     * {@code null} and is an {@code Integer} object that
     * contains the same {@code int} value as this object.
     *
     * @param   obj   the object to compare with.
     * @return  {@code true} if the objects are the same;
     *          {@code false} otherwise.
     */
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
    

    由代码可知,Integer 类型的 hashCode() 方法返回的哈希码就是它的数值;它使用 equals() 方法比较的也是数值是否相同

  • String 是不可变类型,其 hashCodeequals 方法如下:

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    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;
    }
    
    /**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    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 方法利用其值进行创建,所以相同大小的值其哈希码一定相同

  • StringBuffer 是可变类型,它并没有重写 hashCodeequals 方法,所以使用 Object.hashCodeObject.equals 方法

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值