1. 散列表是什么?
定义,摘选自维基百科。
散列表(Hash Table)也叫哈希表。
是根据键(Key)而直接访问在内存存储位置的数据结构
它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
2. 一个例子
通过一个简单的例子解释一下。
老师统计班上同学的成绩,想要把每个分数的人数统计一下,也就是说考了100分的有多少人?考了99分的有多少人?
可能刚接触编程的人会这样写
var score = [...] // 存放了所有学生的成绩
for (var i = 0; i <= 100; i++) {
var sum = 0; // 计算考了i分的同学有多少个
for (var j = 0; j < score.length; j++) {
if (i == score[j]) {
sum++
}
}
console.log('考了' + i + '分的同学有' + sum + '人')
}
这样显然很慢。假设统计全校的学生的四六级分数呢,每次的查询都是O(n)
复杂度
那么我们想想其实可以拿分数来当键值,去访问该分数段的人数。
var total = 1000 // 学生的人数
var res = new Array(total).fill(0)
for (var i = 0; i < score.length; i++) {
res[score[i]]++
}
for (var i = 0; i <= 100; i++) {
console.log('考了' + i + '分的同学有' + res[i]+ '人')
}
这就是一个简单的哈希表。
这个散列函数,很简单
Hash(k) = k
,简单的将分数这个关键字当成key值(直接定址法)
这个记录的数组就叫做哈希表。
可以发现,我们的哈希表,
正常情况下修改,查找和删除的复杂度都是O(1),是很优秀的数据结构
2. 哈希冲突
散列表需要一一对应,也就是说,一个键应该对应一个值
如果一个键对应了多个值,那么就会出现冲突,这种冲突叫哈希冲突
那么解决比较好的方法就是拉链法
。
假设score[100]
这个位置出现了a, b
,那就可以将对应值搞成一个链表
a的next域指向b
3. 摘要算法
摘要算法是个啥?听起来很吓唬人,其实它就是一个哈希函数。
3.1 消息摘要
将长度不固定的消息message作为输入参数,运行特定的Hash函数,生成固定长度的输出,这个输出就是Hash,也称为这个消息的消息摘要(Message Digest)
3.2 Hash函数
这个特定的Hash函数,常见的就是MD5
和SHA1
MD5
哈希函数将任意长度的数据摘要(映射)为一个定长的密文
为什么叫摘要而不叫加密呢?因为这个摘要的结果是不可逆的,不能被还原
3.3 安全隐患(碰撞)
理论上来说,不管使用什么样的摘要算法,必然存在2个不同的消息,对应同样的摘要。
因为输入是一个无穷集合,而输出是一个有限集合,所以从数学上来说,必然存在多对一的关系
简单说,也就是如果有一个A 对应 B,因为B是固定的,我一定能再找到一个C也对应B
这只是理论上的,安全的Hash算法几乎是无法破解的。
3.4 用处
数据完整性验证
假设一个软件QQ,开发人员开发完成后
- 对QQ这个软件通过MD5哈希函数得到一个MD5的值,将这个MD5值公布出去
- 用户下载这个软件,如果别人修改了这个QQ软件中的任何内容,或者软件下载途中缺少了某些内容,那么下载完成后,我们对这个文件重新计算MD5的时候,就会发现与QQ官方公布的MD5值不同,这个时候我们就知道我们拿到的QQ数据不完整了。
保护用户密码
原理也是一样的。
假设数据库里存储的用户密码都是明文,如果有坏人攻破了数据库,就轻松拿到了用户密码等信息
那我们就可以通过MD5来保护用户密码
数据库中不存储用户的密码,转而存储用户密码经过MD5计算后的MD5值
用户登录的时候,只要将密码进行MD5计算,与数据库中的MD5值比对,相同即可登录
这样一来,即使数据库密码泄露,坏人也只能拿到计算过后的MD5值,并不能反向推导出用户密码