为什么重写equels方法时,一定要重写hashcode方法

目录

■前言

■java中为什么重写equals方法,就一定要重写hashcode方法

■重写hashcode方法,例子代码(使用到了素数31)

■为什么初始选择17,为什么用31乘

■JVM默认是如何计算hash值的

■Object默认的hashcode方法和equals方法

native int hashcode中,native的含义

■String重写的equals方法

■String重写的hashcode方法(也使用了素数31)

■HashMap使用时,hashcode调用 与 equals调用

一个键值对放入 HashMap 中时

hashmap取值时

■Hash碰撞

什么是hash碰撞

如果经常发生hash碰撞,对HashMap的使用有什么影响

如何避免hash碰撞


=========

■前言

・最近刷一刷java考试题

・而且,前一段时间,同事编码也遇到了这种问题(equals相等,hashcode不相等。)、因此造成了HashMap取不到想要的值。

■java中为什么重写equals方法,就一定要重写hashcode方法

在Java中,通常在重写equals方法的同时也需要重写hashCode方法,这是因为这两个方法是密切相关的。

当你在Java中使用对象作为键的时候,比如HashMap、HashSet等,这些数据结构会根据对象的hashCode来进行快速的查找和定位。如果两个对象在equals方法中被认为是相等的,那么它们的hashCode应该是相等的。因此,如果不重写hashCode方法,那么当对象的equals方法返回true时,由于hashCode不同,就会导致对象在Hash数据结构中无法正确地被定位到,导致错误的行为。

因此,重写equals方法时,也需要确保重写hashCode方法,以确保对象在作为键的时候能够正确地被定位。

■重写hashcode方法,例子代码(使用到了素数31)

public class MyClass {
    private String name;
    private int age;

    // 构造函数等其它方法省略

    @Override
    public int hashCode() {
        int result = 17; // 选择一个常数,比如17,作为乘法因子的初始值
        result = 31 * result + name.hashCode(); // 用31乘上前面的结果再加上字段的hashCode
        result = 31 * result + age; // 用31乘上前面的结果再加上字段的hashCode
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        MyClass other = (MyClass) obj;
        return age == other.age && (name == null ? other.name == null : name.equals(other.name));
    }
}

===

在这个示例中,我们用到了以下常见的实践:

  1. 选择一个初始的乘法因子(通常选择17)。
  2. 用字段的hashCode值与乘法因子相乘再加到结果中,在每次迭代中用不同的乘法因子。
  3. 在equals方法中使用的字段,在hashCode方法也需要使用并合适地处理null值。
  4. 最后返回计算得到的结果作为hashCode。

===

为什么初始选择17,为什么用31乘

【数】 prime number  素数

质数(素数) 与 加密(密码学上的应用)_c#加密质数-CSDN博客

==

选择17是因为它是一个较小的质数,并且大多数情况下都能产生良好的结果。由于hashCode的初衷是要尽量分散地分配哈希值,选择一个质数可以减少发生碰撞的可能性。

===

选择31作为乘法因子是因为它是一个奇数,并且它的乘法运算能够被编译器优化为移位运算和减法运算,这能够有效地提高计算性能。同时,31也是一个素数,能够帮助尽可能地避免哈希值的碰撞。

===

综合来说,选择17和31作为乘法因子的初始值和乘数是一种相对经验丰富的编程实践,能够保证产生较好的哈希值并且在大部分情况下保持高效。

===

请注意,实际的hashCode计算方式可以根据具体情况进行调整!!!

归纳为两个关键点

  1. 选择一个质数作为乘法因子,以减少哈希值的碰撞可能。这可以帮助尽量分散地分配哈希值,减少碰撞的概率。

  2. 选择一个适合的乘数,一般来说是一个素数,并能够被编译器优化为移位运算和减法运算,从而提高计算性能。

==

JVM默认是如何计算hash值的

JVM默认使用的哈希函数是通过将对象的内存地址进行转换来计算哈希值。这种方法的优点是快速且唯一,但缺点是如果两个对象不同但地址相同,则它们的哈希值也会相同,可能导致哈希冲突。因此,在实际开发中,一般会重写hashCode()方法来根据对象的内容来计算哈希值,以避免潜在的冲突。

■Object默认的hashcode方法和equals方法

为什么重写equels方法时,一定要重写hashcode方法_第1张图片

==

native int hashcode中,native的含义

在Java中,使用native关键字修饰的方法表示这个方法的实现是由底层的非Java语言(通常是C或C++)来实现的。这样的方法被称为本地方法,它们的具体实现是由底层语言编写的,并且可以通过Java的本地方法接口(JNI)来进行调用。

Object类中,hashCode()方法被标记为native,这意味着它的具体实现是由底层语言来完成的。这通常是因为hashCode()方法的实现需要直接访问底层的内存或其他系统资源,这超出了Java语言本身的能力。

==

String重写的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;
    }

String重写的hashcode方法(也使用了素数31)

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

==

HashMap使用时,hashcode调用 与 equals调用

一个键值对放入 HashMap 中时

・当你尝试将一个键值对放入 HashMap 中时,HashMap 会首先调用键对象的 hashCode 方法来确定该键值对的存储位置。
・如果发生了哈希冲突(即两个不同的键具有相同的哈希码值),HashMap 将会通过调用键对象的 equals 方法来精确地确定是否已经存在相同的键。

hashmap取值时

HashMap 在取值时会先调用hashCode方法来确定存储桶的位置,然后调用equals方法来精确地确定键值是否匹配。因此,两个方法都会被调用。hashCode方法用于确定存储位置,equals方法用于确定键值是否匹配。

==

Hash碰撞

什么是hash碰撞

哈希碰撞是指当两个不同的输入数据经过哈希函数处理后产生相同的哈希值。这种情况在哈希函数的设计中是不可避免的,因为哈希函数需要把无限大小的输入空间映射到有限大小的输出空间,这就导致了可能存在多个不同的输入映射到相同的哈希值。哈希碰撞可能会对密码学安全性和数据完整性产生影响,因为攻击者可以通过构造具有相同哈希值的不同输入数据来欺骗系统或者破坏数据完整性。因此,好的哈希算法应该尽可能地减少哈希碰撞的概率。

如果经常发生hash碰撞,对HashMap的使用有什么影响

哈希碰撞会影响 HashMap 的性能。当发生哈希碰撞时,HashMap 将会遇到两个不同的键映射到相同的哈希表位置的情况,这会导致链表长度的增长。随着链表长度的增加,HashMap 的性能可能会受到影响,例如在查找、插入和删除操作时的时间复杂度可能会变高。

---------------

为了应对哈希碰撞带来的性能问题,Java 在 JDK 8 中引入了树化机制。当链表长度过长时,HashMap 将链表转化为红黑树,以提高操作的效率。这种优化在解决哈希碰撞对性能的影响方面起到了积极作用。然而,为了尽可能降低哈希碰撞的发生率,仍然需要选择合适的哈希函数和适当调整 HashMap 的容量和负载因子。

如何避免hash碰撞

避免哈希碰撞通常需要采取以下措施:

  1. 良好的哈希函数:选择一个高效而且能够均匀分布键的哈希函数是很重要的。这意味着哈希函数应该尽可能少地生成冲突。

  2. 良好的负载因子和容量:根据数据规模选择适当的负载因子和初始容量,这可以减少碰撞的概率。负载因子是指在哈希表扩容之前,填充因子占用的比例,通常情况下在0.75左右是一个比较合适的值。

  3. 开放地址法和链地址法:哈希碰撞的解决方案包括使用开放地址法和链地址法。开放地址法在哈希碰撞发生时会尝试寻找空的位置来存放冲突元素,而链地址法则是在哈希表中的每个位置构建一个链表或者红黑树来存放所有哈希值相同的元素。

  4. 数据完全分布:如果能够确保键值对的均匀分布,可以大大减少碰撞的概率。这可能需要对数据的结构进行优化,或者对哈希函数进行适当调整。

综上所述,避免哈希碰撞需要选择合适的哈希函数、负载因子和容量,并且可以使用适当的解决方案来处理碰撞的发生。

===

你可能感兴趣的:(java,算法,java,算法)