数据结构与算法-散列表、哈希算法
散列表
散列表⽤的是数组⽀持按照下标随机访问数据的特性,所以散列表其 实就是数组的⼀种扩展,由数组演化⽽来。可以说,如果没有数组,就没有散列表。
组成
散列函数
Hash(key)
- 直接定址法:Hash(key)=a*key+b (a、b为常数)
- 数字分析法
- 平方取中法
- 折叠法
- 除留余数法:Hash(key)=key mod p (p是一个整数(最好是小于等于表长的质数))
- 随机数法
散列冲突
- 开放定址法(Open addressing)
- 线性探测法:一旦冲突,就找下一个地址,直到找到空地址存入,增量序列为1,2,3,4…
- 二次探测法:一旦冲突,就找下一个地址,直到找到空地址存入,增量序列为12,-12,22,-22…,q2
- 伪随机探测法:一旦冲突,就找下一个地址,直到找到空地址存入,增量序列为伪随机数
数据量比较小、装载因子小的时候,适合采用开放定址法
- 拉链法(chaining) 相同散列地址的记录链成一个单链表
基于链表的散列冲突处理⽅法⽐较适合存储⼤对 象、⼤数据量的散列表,⽽且,⽐起开放寻址法,它更加灵活,⽀持 更多的优化策略,⽐如⽤红⿊树代替链表
⼯业级散列表举例因素分析
- 初始⼤⼩
- 装载因⼦和动态扩容
- 散列冲突解决⽅法
- 散列函数
应用实例
LRU缓存淘汰算法
需要的操作:
- 往缓存中添加⼀个数据;
- 从缓存中删除⼀个数据;
- 在缓存中查找⼀个数据。
解决方案:
- LinkedHashMap,在散列的基础上,增加添加元素有序
Redis有序集合
需要的操作:
- 添加⼀个成员对象;
- 按照键值来删除⼀个成员对象;
- 按照键值来查找⼀个成员对象;
- 按照分值区间查找数据,⽐如查找积分在[100, 356]之间的成员 对象;
- 按照分值从⼩到⼤排序成员变量;
解决方案:
- 散列+跳表
LinkedHashMap
LinkedHashMap是通过双向链表和 散列表这两种数据结构组合实现的。LinkedHashMap中 的“Linked”实际上是指的是双向链表,并⾮指⽤链表法解决散列冲突
哈希算法
将任意⻓度的⼆进制值串映射为固定⻓度的⼆进制值串,这个映射的规则就是哈希算法,⽽通过原始数据映射之后得到的⼆进制值串就是哈希值
需要满足的条件
- 从哈希值不能反向推导出原始数据(所以哈希算法也叫单向哈希 算法);
- 对输⼊数据⾮常敏感,哪怕原始数据只修改了⼀个Bit,最后得到 的哈希值也⼤不相同;
- 散列冲突的概率要很⼩,对于不同的原始数据,哈希值相同的概 率⾮常⼩;
- 哈希算法的执⾏效率要尽量⾼效,针对较⻓的⽂本,也能快速地 计算出哈希值。
应用
安全加密
- MD5(MD5 Message-Digest Algorithm,MD5消息 摘要算法)
- SHA(Secure Hash Algorithm,安全散列算法)
- DES(Data Encryption Standard,数据加密标准)
- AES(Advanced Encryption Standard,⾼级加密标准)
发生脱库解决方案:
- 主要为了增加破解难度
- 要维护⼀个常⽤密码的字典表,把字典中的每个密码⽤哈 希算法计算哈希值,然后拿哈希值跟脱库后的密⽂⽐对
- 引⼊⼀个盐(salt),跟⽤户的密码组合在⼀ 起,增加密码的复杂度
唯一标识
获取数据固定片段(如前100、中100、后100字节)进行哈希算法,根据哈希值再进行判断可以节省大量计算。
数据校验
基于P2P协议文件下载,将一个文件分为多个文件块,可以对每个文件块进行哈希算法,保证数据安全。
散列函数
保证哈希冲突小,出现冲突时,使用开放定址法、拉链法解决冲突。
负载均衡
保证会话粘滞(Session sticky),需要在同⼀个客户端上,在⼀次会话中的所有请求都 路由到同⼀个服务器上。
可以通过 哈希算法,对客户端IP地址或者会话ID计算哈希值,将取得的哈希值 与服务器列表的⼤⼩进⾏取模运算,最终得到的值就是应该被路由到 的服务器编号。
数据分片
两个例子
- 如何统计“搜索关键词”出现的次数?
以先对数据进⾏分⽚,然后采⽤多台机器处 理的⽅法,来提⾼处理速度;
为了提⾼处理的速 度,我们⽤n台机器并⾏处理。我们从搜索记录的⽇志⽂件中,依次读 出每个搜索关键词,并且通过哈希函数计算哈希值,然后再跟n取模, 最终得到的值,就是应该被分配到的机器编号。
- 如何快速判断图⽚是否在图库中?
同样可以对数据进⾏分⽚,然后采⽤多机处理。我们准备n台机 器,让每台机器只维护某⼀部分图⽚对应的散列表。我们每次从图库 中读取⼀个图⽚,计算唯⼀标识,然后与机器个数n求余取模,得到的 值就对应要分配的机器编号,然后将这个图⽚的唯⼀标识和图⽚路径 发往对应的机器构建散列表;
当要判断⼀个图⽚是否在图库中的时候,我们通过同样的哈希算 法,计算这个图⽚的唯⼀标识,然后与机器个数n求余取模。假设得到 的值是k,那就去编号k的机器构建的散列表中查找。
分布式存储
问题原因:
-
但是,如果数据增多,原来的10个机器已经⽆法承受了,我们就需要 扩容了,⽐如扩到11个机器,这时候⿇烦就来了;
-
原来的数据是通过与10来取模的。⽐如13这个数据,存储在编号为3 这台机器上。但是新加了⼀台机器中,我们对数据按照11取模,原来13这个数据就被分配到2号这台机器上了。
解决方案:⼀致性哈希算法
- 假设有k个机器,数据的哈希值的范围是[0, MAX]。我们将整个范 围划分成m个⼩区间(m远⼤于k),每个机器负责m/k个⼩区间。当 有新机器加⼊的时候,就将某⼏个⼩区间的数据,从原来的机器 中搬移到新的机器中。这样,既不⽤全部重新哈希、搬移数据,也保持了各个机器上数据数量的均衡;
- ⼀致性哈希算法的基本思想就是这么简单。除此之外,它还会借助⼀ 个虚拟的环和虚拟结点,更加优美地实现出来;