哈希值的计算方式及一些相关概念

以字符串"123"为例:字符’1’的ascii码是49。

hashCode = (49*31 + 50)*31 + 51或者这样看:hashCode=(‘1’ * 31 + ‘2’ ) * 31 + '3’可见实际可以看作是一种权重的算法,在前面的字符的权重大。

hashCode 源码:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                //上次一算出的hash值乘以31 然后再加上当前字符编码值
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

也可以这样计算:

  • hashCode = s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。空字符串的哈希值为 0。

这样有个明显的好处,就是前缀相同的字符串的hash值都落在邻近的区间。
好处有两点:

  • 1.可以节省内存,因为hash值在相邻,这样hash的数组可以比较小。
  • 2.hash值相邻,如果存放在容器,比好HashSet,HashMap中时,实际存放的内存的位置也相邻,则存取的效率也高。(程序局部性原理)以31为倍数,原因了31的二进制全是1,则可以有效地离散数据 。

相关概念:

1、Hash值有什么用?
HashMap、HashTable、HashSet,所以涉及到使用Hash值进行优化存储的地方,都会用到HashCode。HashCode是Key,这种计算为提高计算的性能。想想看,一般来说,数组算是比较快的集合类了吧,直接用index定位元素,简直就是O(1)的级别。但是添加元素就不这么乐观了。但是使用hash类的集合,添加元素,移动的元素少,只影响一小块,并且查找元素,由于hash值已经进行了定位分组,所以也会大大缩小涉及面,快速定位。

2、Hash值应该符合什么原则?
A、等幂性。不管执行多少次获取Hash值的操作,只要对象不变,那么Hash值是固定的。如果第一次取跟第N次取不一样,那就用起来很麻烦,需要记录当前是第几次操作,这种需要记录状态的事情,可不是什么好事。 B、对等性。若两个对象equal方法返回为true,则其hash值也应该是一样的。举例说明:若你将objA作为key存入HashMap中,然后new了一个objB。在你看来objB和objA是一个东西(因为他们equal),但是使用objB到hashMap中却取不出来东西。 C、互异性。若两个对象equal方法返回为false,则其hash值最好也是不同的,但这个不是必须的,只是这样做会提高hash类操作的性能(碰撞几率低)。

3、Hash值为何这样计算?
A、简单计算就是组成成员的hash值直接相加即可。比如ObjectA有三个属性,propA、propB和propC,最直接的计算方式就是propA.hashcode+propB.hashcode+propC.hashcode。 B、但是如果遇到有顺序相关的怎么办?比如String类型是由char数组组成,并且这些数组是有顺序的。如果使用第一种计算方法,则“ABCD”和“BCDA”就会产生同样的hashCode,那么怎么办呢?最直接想到的办法就是加权,不同的index加不同的权值,这个权值的确定最直接的方法就是某个常数值的几次幂。比如为String的计算hash值为在这里插入图片描述

K的选择也有说法,最好不要是偶数,因为偶数的相乘会造成信息的丢失(乘以2就是左移1位,一旦溢出就会造成信息的丢失,这种计算会造成溢出后的值与某个看似不相关的数值得到的结果是一样的),所以最好是奇数,在这一点上比较推荐使用7,因为7=8-1=2^3-1,
这样计算的时候,直接左移几位再进行一次普通的加减法即可(Java中常用的是31(32-1=2^5-1))。

注意一下几点:

  1. ==在判断对象时,其实是根据对象在堆栈中的地址判断对象是不是一样,而不是根据hashcode 值 也就是说,只有用a==b时比较的才是ref,也就是说这时才是比较是a与b是不是同一个对象(地址)。
  2. 字符串hash函数,不仅要减少冲突,而且要注意相同前缀的字符串生成的hash值要相邻。
  3. hashSet中比较是否重复的依据是a.hasCode()=b.hasCode() && a.equals(b)
  4. String的hashCode依据: 以依赖于char[i]的int值以和char[i]的排列顺序的算法计算出的。不依赖String的ref.
  5. String的equals依据: a==b || ( a.length=b.length && { a[i]=b[i] } )
  6. 两个不同ref的String可能会被认为是集合中的同一个元素(冲突),链式地址解决冲突。
  7. equals相等时,hashcode也应该相等在分布式计算中为了解决 增加node或减少node带来的hashcode变化,引入了一致性hash算法,用一个环形链表将结点变化引发的hash值改变降到局部最低。

参考:https://blog.csdn.net/zhu_liangwei/article/details/7852014

猜你喜欢

转载自blog.csdn.net/m0_47305552/article/details/107896804