Hash系列目录
博客创建时间:2020.06.11
博客更新时间:2021.02.23
1. Hash
Hash也称散列、哈希,对应的英文都是Hash,它既是一种重要的存储方式(散列),也是一种常见的检索方法。
Hash方法的主要思想是根据结点的关键码值来确定其存储地址,它有两个重要的概念散列表(hash table )和散列函数(hash function)。
注意当说起Hash时就要说到hash表和hash算法,否则单一拿来说无多大实际意义。
2. 散列表
数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。
哈希表则能综合数组和链表的优点,做到增删查改都容易
散列表又名hash table,是按散列存储方式构造的一种物理存储结构,因应用了哈希算法被称为哈希表。它是一种线性数据结构,其中的元素不一定紧密排列,而是可能存在空隙。哈希表是基于快速存取的目的设计的,拥有查找速度快的特性。
槽
哈希表中的一个单元称作槽(slot),它的对应地址被叫做散列地址/哈希地址。
以元素X的key为自变量,通过一定的散列函数h(X.key),计算出对应的函数hash值来(hash值就是key的指纹,是一个二进制串),那么hash值就是在hash table中的存储地址,也即槽。
数据在插入检索时,如要插入数据X先计算其散列地址,然后将X存在该处。同样在检索时用同样的方法计算hash值,然后到相应的槽去取要找的元素X。通过散列方法可以对结点进行快速高效检索。
负载因子
如果我们存储70个元素,但我们可能为这70个元素申请了100个元素的散列表空间。70/100=0.7,0.7即为负载因子,其范围0~1,也叫装填因子。
设散列表的空间大小为M,填入表中的结点数为N, 则 N/M 称为散列表的负载因子(load factor,也有人翻译为“装填因子”)。
在一般情况下,散列表的空间必须比结点的集合大,此时虽然浪费了一定的空间,但换取的是检索效率。也即一般负载因子会设计的稍微大一些。
实现原理
一般情况下,散列表的存储空间是一个一维数组HT[M](注意元素可能不是连续的
),散列地址是数组的下标,M是数组的大小。
设计散列方法的目标,就是设计某个散列函数h,0<=h( Key ) < M。对于关键码值K,得到HT[i] = Key 。建立散列表时,则关键码与散列地址是一对一的关系。
例如:给定一个字符串键Key为 "str",该键对应的元素值Value是"jack"。
通过hash算法对"str"进行加工生成一个存储地址HT(i),其对应内容存储着"jack"。
根据设定的哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上,每个关键码值一一对应一个哈希地址,地址中记录数据值value。数据通过H(Key)获得其数据保存地址,然后拿到数据值value方式能够加快数据的查找速度。同理也能快速插入数据
使用hash table 存取元素时不会像传统的数据结构逐个遍历、一一对比,而是通过哈希算法直接获取元素的存储地址,因此哈希表会比传统的数据结构更为高效,这也是使用哈希表的原因之一。
性能分析
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。
对散列表查找效率的量度,依然用平均查找长度来衡量,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。
因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
- 散列函数是否均匀;
- 处理冲突的方法;
- 散列表的装填因子。
3. 散列函数
散列函数,也叫哈希函数、或者hash算法,不一定是数学函数。散列函数是记录存储位置和关键字Key之间建立一个明确的对应关系f 函数。每个关键值Key和存储位置正常用情况下(hash碰撞下不正常
)是一一对应的,非常适合用于查找。
它是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列地址、哈希地址、hashCode。也即将任意长度的消息压缩到某一固定长度的消息摘要的函数。
这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能根据散列值来唯一确定输入值。
当不同的输入产生相同的输出HashCode时,则发生了Hash碰撞,好的hash函数就需要尽量避免出现hash碰撞
HashCode
哈希值也即哈希地址,哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。
散列函数优缺点
优点:
- 散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位。因为散列法将元素特征转变为数组下标。
缺点:
- 会存在关键字重复的问题,比如说男女为关键字的时候就不合适了。
- 同样不适合查找范围的,比如说查找18-20岁之间的同学。
散列函数特性
它有两个重要的特性需要我们重视,碰撞和抗篡改。
抗篡改
-
从hash值不可以反向推导出原始的数据。这个从上面MD5的例子里可以明确看到,经过映射后的数据和原始数据没有对应关系
-
输入数据的微小变化会得到完全不同的hash值,相同的数据会得到相同的值,如下:
输入:
System.out.println("内容的hash值 =".hashCode());
String string2 = "内容的hash值 =";
System.out.println( string2.hashCode());
System.out.println("内容的hbsh值 =".hashCode());
输出:
627393883
627393883
656023034
碰撞
- 任一hash函数都会出现哈希冲突/哈希碰撞,在后面会专门讲解哈希冲突。
散列函数构造选取
Hash函数有很多种,开发中经常使用的MD5和SHA都是历史悠久的Hash算法。优秀的散列函数的选取原则需要考虑各种因素,其常见的因素有如下几种:
- 关键码长度,关键码长也能快速计算出哈希值
- 散列表大小
- 关键码分布均匀,尽量让不同的关键码有不同的函数散列值
- 记录的检索频率
- 运算尽可能简单,执行的效率要高
- 函数的值域必须在散列表范围内
- hash算法的哈希碰撞率要低
“好的散列函数 = 计算简单 + 分布均匀”。
其中计算简单指的是散列函数的计算时间不应该超过其他查找技术与关键字比较的时间,而分布均匀指的是散列地址分布均匀。
总结
本篇博文主要对Hash的基本知识点哈希表、哈希函数进行了分析讲解,对于哈希冲突和哈希函数的运用等,将在下一篇博文中《Android面试Hash原理详解二》进行讲解。
补充相关的存储知识
1. 物理存储结构共4种:顺序、链式、索引、散列
2. 其中顺序和链式最常见,这两种存储结构的共同特征是元素之间有着映射关系
3. 而哈希表(散列存储结构)的元素之间相互独立。
4. 索引存储结构类似现实世界中的字典目录。
相关链接:
扩展链接:
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !