HashMap是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在。在HashMap中,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置,我们总是可以通过key快速地存、取value。在学习HashMap之前先来了解几个概念。
Hash的定义:
Hash,一般翻译做“散列”,也有直接音译为“哈希”的。它是将一个任意长度的二进制值通过一个映射关系转换成一个固定长度的二进制值,任意长度的二进制值和固定长度的二进制值存在一一对应关系(就是key–value的关系)。
Hash表(取数据的时间复杂度为1):
又叫散列表,通过一个 key 映射到表中的一个位置,来找到与这个key对应的唯一映射的Value,加快查询的速度,这个映射函数叫做Hash函数,存放记录的数组叫做散列表。
构造Hash函数:
由于HashMap的内部实现是使用了除留余数法因此只讲解除留余数法(感兴趣可以查看这篇博客)。
除留余数法(最常用的构造散列函数方法):
f(key) = key mod p (p≤m),m为散列表长。根据经验,若散列表表长为m,通常p为小于或等于表长(最好接近m)的最最大质数,可以更好的减小冲突。 若表的长度为16(最接近16的素数为15,key为传入的值通过特定的算法获取到的值,value为要存取的值,index 为在Hash表中的坐标) key = 1,value=23,index=1%15= 1; key=17,value=22,index=17%15=2 ;
在Hash表中的储存状态:
Hash表处理冲突:
1.冲突产生的原因:
不同的key用同样的Hash算法,可能会得到相同的Hash值
a.线性探测法:一个key值通过散列函数hash(key),找到关键字key在Hash表中的位置,如果当前位置已经有了一个关键字,就产生了哈希冲突,那么就继续往后进行探测,直到当前位置没有关键字的存在。若存在一下三个数 a,b,c (其中 a 和 c 的通过 Hash 函数的到了 Hash 值都为3, c 的得到 Hash 值为2 ):
那么他们在Hash表中的储存状态为:
先将 a,b 按照计算出的Hash值存入Hash表的对应位置;由于c的Hash值为2,先探测 2 位置,2 位置存在值;探测 4 位置,没有值,将 c 存入4位置。拉链法(HashMap中使用的这种方法来进行冲突的处理)
拉链法的数据结构 我们都知道,数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;而链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。拉链法利用了它们的优点,有数组和链表构成,既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
拉链法解决冲突的做法: 将具有相同Hash值的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。 【例】设有 m = 5 , H(K) = K mod 5 ,关键字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外链地址法所建立的哈希表如下图所示:
将 5 插入到 T0 位置。由于 15 和 5 有相同的 Hash 值,产生冲突。临时保存 T0 的地址令 T0 = 15将临时保存的地址连接到 T0 位置。拉链法的优点:
拉链法处理冲突简单,且无堆积现象,即具有相同 Hash 值的Key 也不会发生冲突,因此平均查找长度较短;由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;拉链法的缺点:
指针需要额外的空间,故当结点规模较小时,需要提供额外的指针域,浪费了一定的空间。新闻热点
疑难解答