哈希表Java中HashMap
Posted 有且仅有
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈希表Java中HashMap相关的知识,希望对你有一定的参考价值。
哈希算法,是一类「算法」。
哈希表(Hash Table),是一种「数据结构」。
哈希函数,是支撑哈希表的一类「函数」。
Map
是映射/地图的意思,在Java中Map
表示一种把K
映射到V
的「数据类型」。HashMap
,是Java中用哈希表实现的一种「Map
」。
一、Hash算法
1. 是什么?
查下词典:
hash 英 [hæʃ] 美 [hæʃ]
n. 剁碎的食物;混杂,拼凑;重新表述
vt. 搞糟,把…弄乱;切碎;推敲
n. (Hash)人名;(阿拉伯、保、英)哈什;(西)阿什
「hash」一词我觉得叫「切碎」比较合适,但正式上会被称为「散列」,大部分时候也叫「哈希」,据说是因为最早翻译的人以为这是某个叫Hash 的人发明的算法,所以音译了其名字。
接下来我们先给出定义,Hash算法 是这样一类算法:
这类算法接受「任意长度的二进制输入值」,对输入值做换算(切碎),最终给出「固定长度的二进制输出值」。
以更好理解的方式来说,Hash算法 是摘要算法 :它从不同的输入中,通过一些计算摘取 出来一段输出数据,且这个值可以用以区分输入数据。
所以,MD5 可能是最著名的一种Hash算法 了。
回顾一下:Hash算法 不是某个固定的算法,它代表的是一类算法。
2. 有什么用?
那么,具体来说Hash/摘要/散列/切碎算法 有哪些用处呢?
「信息安全」领域
Hash算法 可用作加密算法。
如文件校验:通过对文件摘要,可以得到文件的「数字指纹」。你从网络上下载的任何副本的「数字指纹」只要和官方给出的「数字指纹」一致,那么就可以知道这是未经篡改的文件。例如著名的MD5 。
「数据结构」领域
Hash算法 通常还可用作快速查找。
这是今天我想说的部分,根据Hash函数 我们可以实现一种叫做哈希表(Hash Table) 的数据结构。这种结构可以使得我们可以实现对数据进行快速的「存」和「取」。
以上我们了解了Hash算法 有什么用,接下来我们就来具体看看Hash算法 的重要的应用场景 —「数据结构-哈希表」。
二、哈希表
1. 什么是哈希表?
首先想一个问题:我们之前是如何在「数据结构」中做「查找」的呢?
「线性表、树」: 在线性表、树 这些结构中,记录 在结构 中的相对位置是随机的,和记录的关键字之间不存在确定关系。因此,在结构中查找时需要进行一系列和关键字的「比较」,即这一类查找方法建立在「比较」的基础上。在顺序查找时,比较的结果为"="
与"≠"
2种可能;在折半查找、二叉排序树查找和B-树查找时,比较的结果为"<", "=", ">"
3种可能。此时,查找的效率依赖于查找过程中所进行的「比较次数」。
「引出Hash表」:理想的情况是希望不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的关系 f f ,使每个关键字和结构中一个唯一的存储位置相对应。因而在查找时,只要根据这个对应关系找到给定值 K K 的像。若结构中存在关键字和 K K 相等的记录,则必定在的存储位置上,反之在这个位置上没有记录。由此,「不需要比较」便可直接取得所查记录。在此,我们称这个对应关系 f f 为:哈希(Hash)函数,按这个思想建立的映射关系表为:哈希表。
(插播:记得「理想情况」这几个字~~ 这会在后文给出解释)
这是《数据结构(C语言版)》[1]中引出哈希表的一段描述,通俗易懂。至此,我们知道了什么是哈希函数和哈希表,下面再继续扩充描述如下:
「哈希函数」的特点:
灵活
哈希函数是一个映像,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许的范围之内即可。
冲突
对不同的关键字可能得到同一哈希地址,即,而 f(key1)=f(key2) f ( k e y 1 ) = f ( k e y 2 ) ,这种现象称为「冲突(collision)」。
冲突只能尽量地少,而不能完全避免。因为,哈希函数是从关键字集合到地址集合的映像。而通常关键字集合比较大,它的元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。因此,在实现哈希表这种数据结构的时候不仅要设定一个“好”的哈希函数,而且要设定一种“处理冲突的方法”。
「哈希表」的正式定义:
根据设定的Hash函数 - H(key) H ( k e y ) 和处理冲突的方法,将一组关键字映象 到一个有限的连续的地址集(区间)上,并以关键字在地址集中的象 作为记录在表中的存储位置,这样的映射表便称为Hash表。
2. 哈希函数详细描述
上面我们已经引出了并解释了哈希函数,即「哈希函数」是支撑「哈希表」的「一类函数」。实际工作中,需要视不同的情况采用不同的Hash函数,通常要考虑的因素有:
- Hash函数 执行的时间。
- 关键字 的长度。
- Hash表 的大小。
- 关键字 的分布情况。
- 记录 的查找频率。
有如下一些常用的Hash函数 构造方法:
直接寻址法:
f(k)=k f ( k ) = k 或者 f(k)=a∗k+b f ( k ) = a ∗ k + b
取k 或k 的某个线性函数为Hash地址 。
特点:由于直接地址法相当于有多少个关键字就必须有多少个相应地址去对应,所以不会产生冲突,也正因为此,所以实际中很少使用这种构造方法。
数字分析法:
首先分析待存的一组关键字,比如是一个班级学生的出生年月日,我们发现他们的出生年 大体相同,那么我们肯定不能用他们的年 来作为存储地址 ,这样出现冲突 的几率很大;但是,我们发现月日 的具体数字差别很大,如果我们用月日 来作为Hash地址,则会明显降低冲突几率。因此,数字分析法就是找出关键字 的规律,尽可能用差异数据来构造Hash地址 ;
特点:需要提前知道所有可能的关键字,才能分析运用此种方法,所以不太常用。
平方取中法:
先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
例:我们把英文字母在字母表中的位置序号作为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理我们可以得到关键字“KYAB”、“AKEY”、“BKEY”的内部编码。之后对关键字进行平方运算后,取出第7到第9位作为该关键字哈希地址,如下图所示:
关键字 内部编码 内部编码的平方值 H(k)关键字的哈希地址 KEYA 11050201 122157778355001 778 KYAB 11250102 126564795010404 795 AKEY 01110525 001233265775625 265 BKEY 02110525 004454315775625 315 [2]
特点:较常用。
折叠法:
将关键字分割成位数相同的几部分(最后一部分位数可以不同),然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
随机数法:
选择一个随机函数,取关键字的随机函数值作为Hash地址,通常用于关键字长度不同的场合。即
f(key)=random(key) f ( k e y ) = r a n d o m ( k e y )特点:通常,关键字长度不相等时,采用此法构建Hash函数 较为合适。
除留取余法:
f(k)=k f ( k ) = k mod m o d p p ,
取关键字被某个不大于Hash表 长m 的数p 除后所得的余数为Hash地址 。
特点:这是最简单也是最常用的Hash函数构造方法。可以直接取模,也可以在平法法、折叠法之后再取模。
值得注意的是,在使用除留取余法 时,对p 的选择很重要,如果p 选的不好会容易产生同义词。由经验得知:p 最好选择不大于表长m 的一个质数、或者不包含小于20的质因数的合数。
3. 处理冲突
如何处理冲突是哈希造表不可缺少的一个方面。现在描述一下处理冲突:
假设哈希表的地址集为 : 0−(n−1) 0 − ( n − 1 ) ,那么「冲突」是指 : 由关键字得到的哈希地址为 j(0≤j≤n−1) j ( 0 ≤ j ≤ n − 1 ) 的位置上已存有记录,而「处理冲突」: 就是为该关键字的记录找到另一个「空的哈希地址」。
在处理冲突的过程中可能得到一个地址序列 Hi,i=1,2,...,k(Hi∈[0,n−1]) H i , i = 1 , 2 , . . . , k ( H i ∈ [ 0 , n − 1 ] ) 。处理时,若得到的另一个哈希地址 Hi H i 仍然发生冲突,则再求下一个地址 H2 H 2 ,若 H2 H 2 仍然冲突,再求 H3 H 3 ,依次类推,直至 Hk H k 不发生冲突为止,则 Hk H k 为记录在表中的地址。(注意,此定义不适合链地址法)
冲突处理通常有以下4种方法:
开放定址法:
Hi=(H(key)+di)modJava集合框架中的Hashtable,HashMap,HashSet,哈希表概念