HashTable详解
散列表(Hash table,也叫哈希表),是根据Key value而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录(类似索引),以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
哈希表结构示例
1、数组+链表
2、数组+二叉树(链表树化)
google公司的一个上机题:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的id时,要求查找到该员工的所有信息.
要求:
不使用数据库,速度越快越好=>哈希表(散列)
添加时,保证按照id从低到高插入 课后思考:如果id不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?
使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
思路分析并画出示意图
代码实现[增删改查(显示所有员工,按id查询)]
public class HashTab {
class Emp{
public int id;
public String name;
public Emp next;
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
class EmpLinkedList{//链表
private Emp head;
public void add(Emp emp){
if(head==null){
head=emp;
return;
}
Emp curEmp=head;
while (true){
if(curEmp.next==null){
break;
}
curEmp=curEmp.next;
}
curEmp.next=emp;
}
public void list(){
if(head==null){
System.out.println("kong");
return;
}
System.out.println("为");
Emp curEmp=head;
while (true){
System.out.println(curEmp.id+curEmp.name);
if (curEmp.next==null){
break;
}
curEmp=curEmp.next;
}
System.out.println("完了");
}
}
}
哈希表查找原理
哈希函数
哈希函数其实就是我们常说的哈希算法,主要应用在以下这几个方面:文件校验、数字签名、鉴权协议。常用的哈希算法有以下这些。
<1>MD5:MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。MD5是输入不定长度信息,输出固定长度128bits的算法。
<2>SHA-1:常用于HTTPS传输和软件签名。
<3>SHA-2:SHA-224/SHA-256/SHA-384/SHA-512并成为SHA-2
<4>SHA-3:之前名为Keccak算法,是一个加密杂凑算法。
哈希函数设计的足够好的话,就会减少哈希冲突的概率
如何避免哈希冲突
当不同的键生成了相同的索引的时候-----》 处理冲突------》拉链法和线性探索法。
拉链法
将大小为M的数组的每一个元素指向一个链表,链表中的每一个节点都存储散列值为该索引的键值对,这个就是拉链法。
”John Smith”和”Sandra Dee”通过哈希函数指向152这个索引,该索引又指向了一个链表,在链表中依次存储了这两个字符串。
该方法的基本思想就是选择足够大的M,使得所有的链表都尽可能的短小,以保证查找的效率。对采用拉链法的哈希表实现的查找分为两步,首先是根据散列值找到对应的链表,然后沿着链表的顺序找到相应的键。
线性探索法
线性探测法是开放寻址法解决哈希冲突的一种方法,基本原理为,使用大小为M的数组来保存N个键值对,其中M>N,我们需要使用数组中的空位来解决碰撞冲突。
对照前面的拉链法,在该图中,”Ted Baker”有唯一哈希值153的,但是由于153被”Sandra Dee”占用了。而原先”Sandra Dee”和”John Smith”的哈希值都是152的,但是在对”Sandra Dee”进行哈希的是偶发现152已经被占用了,所以往下找发现153没有被占用,就将其存放在153。后面”Ted Baker”哈希到153上,发现被占用了,就会往下找,发现154没有被占用,所以将其存放到154上面。
开放寻址法中最简单的是线性探测法:当碰撞发生时即一个键的散列值被另外一个键占用时,直接检查散列表中下一个位置,即将索引值加1,这样的线性探测有三种结果:
<1>命中,该位置的键个被查找的键相同;
<2>未命中,键为空;
<3>继续查找,该位置的键和被查找的键不同。
拉链法和线性探索法对比
那个这两种方法的性能上面有什么区别?对于拉链法,查找的效率在于链表的长度,一般我们应该保证长度的M/8-M/2之间,如果链表的长度大于M/2,我们可以扩充数组长度。如果长度在0~M/8时,我们可以缩短数组的长度。对于线性探索法,动态调整数组的大小需要对所有的值重新进行散列并插入新的表中。
不管是拉链法还是散列法,这种动态调整链表或者数组的大小以提高查询效率的同时,还应该考虑动态改变链表或者数组大小的成本。散列表长度加倍的插入需要进行大量的探索,这种均摊成本很多时候需要考虑。
哈希碰撞
不管是采用拉链法还是开放寻址法解决冲突,在后面查找的时候都需要进行多次探测或者查找,在很多时候会使得哈希表的查找效率退化,而不再是常数时间。如下图描述的那样。
哈希攻击就是通过精心构造哈希函数,使得所有的键进过函数函数后都会映射到同一个或者几个索引上,将哈希表退化为一个单链表,这样哈希表的各种操作,比如插入、查找都会从O(1)退化到了链表的查找操作,这样会消耗大量的CPU资源,导致系统无法响应,从而达到拒绝服务供给(Denial of Service,DOS)的目的。