哈希表和HashMap()深入理解

哈希表和hashmap()中关于hash函数理解。

概述

哈希表又称散列表。

基本思路是:把n个关键字为 k i k_i ki(i=1,2,3…n)的元素,根据关键字 k i k_i ki 在哈希函数上的映射,存到长度为 m 的连续内存单元中去,这样的线性存储结构为哈希表。

哈希函数hash() :——以关键字 k i k_i ki为自变量,通过hash()映射到内存单元地址 h ( k i ) h(k_i) h(ki),即哈希地址为因变量,取值为(0-m-1)。,在HashMap中 k i k_i ki一般为key对象的hashcode() 。

问题:在映射过程中,可能出现关键字 k i k_i ki不同,但得到的哈希地址相同的情况,这种现象叫做哈希冲突。我们需要尽量减少这种冲突,使得哈希地址尽量不一样,这样线性表才会更加散列,均匀。

核心:哈希函数

1. 直接定址法

哈希函数 为:
      h ( k ) = k + c h(k) = k+c h(k)=k+c

适用于:关键字分布连续的情况。较少用。

2. 除留取余法

哈希函数 为:
      h ( k ) h(k) h(k) = k k k   m o d mod mod  p ( p < = m ) (p<=m) (p<=m)  m为线性表长度
除留取余法计算简单,较为常用,p取奇数比偶数好,当p取<=m的素数时效果最好。

3.改进1

改进后哈希函数 为:
      h ( k ) h(k) h(k) = k k k   &  ( m − 1 ) (m-1) m1  m为线性表长度。
原因:1、除法效率低。& 运算比 % 效率高
2、 k k k  &  ( m − 1 ) (m-1) m1 = k k k   m o d mod mod p
&运算结果和除留取余法一样的。
前提: m= 2 x 2^x 2x ,即线性表长度必须为2的整数倍幂
效率更高,绝大多数情况下length一般都小于2^16=65536

为什么必须 是2的整数幂?
因为 m = 2 x m=2^x m=2x 时,m-1表示为二进制数才为 x个1。
例如: 2 3 − 1 = 7 2^3-1=7 231=7,转化为二进制为1111,3个1.
       2 16 − 1 = 65535 2^{16}-1=65535 2161=65535,转化为二进制位1111 1111 1111 1111, 16个1。
这样 k k k   &  ( m − 1 ) (m-1) m1 就总是将k的 低x位 和 x 个 1 进行 与运算。即将k的低16位与线性表长度-1(m-1)进行与运算。这样才能保留低位的1。

4.改进2

为了达到更好的散列效果:原来是将key的低16位与线性表长度-1(m-1)进行与运算。但是一般Map中存放value的线性表长度很小,那么与运算的结果就会很多值是相同的,达不到散列的效果,所以让key值的高16位与key本身进行异或运算这样,得到的结果重复值会较少,散列效果好。所以哈希函数为:
      h ( k ) h(k) h(k) = k k k   ^  ( k > > > 16 ) (k>>>16) k>>>16

在java 中查看 HashMap()类的源码发现 hash()函数是这样定义的:

// hash()方法
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }

其中 ^ 异或运算, >>> 是右移位

java中int类型占4个字节,共32位。
7 >>>2 是将7的二进制向右移动两位,左边高位补0。如下:

  0000 0000 0000 0000 0000 0000 0000 0111 
>>>2
  0000 0000 0000 0000 0000 0000 0000 0001

所以h>>>16就是将h向右移动16位,即取h的高16位。
^ 异或运算比 & 和 | 运算效果更好。因为 & | 运算结果会趋于0 或1 。

HashMap之所以能根据get(key)直接拿到value,原因是它内部通过用一个大数组存储所有value,并根据hashcode()函数计算出value应该存储在数组中的索引,这样通过哈希表实现查找。

在Map<key,value>中,一个Entry对象包括:key,value,hash值, 指向下一个Entry对象的引用。两个相同的Key对象(指内容相同即调用equals方法返回true,所以如果key是自定义的类型,必须重写equals方法) 返回的数组索引,即hash值一定是相同的。但两个不同的key对象返回的数组索引应该尽量不同(为什么是尽量?因为会出现不同的key值对应的索引是相同的,这就是冲突。)

遇到冲突怎么办?

这时,就把重复的添加到同一hash值的元素的后面,让 next引用指向它,这样形成一个链表。所以数组索引处可以认为存放的就是一个链表。没有发生冲突的索引处就是只有一个元素的链表。JDK8以后,当链表长度大于8时,就转化为红黑树,这样就大大提高了查找效率。
所以HashMap要想快速查找关键就是让hash值不冲突,这样就不用遍历链表,才能提高效率。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值