1. 简介
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表的实现:数组+链表、数组+二叉树…
图例:
2. 案例实现
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的id时,要求查找到该员工的所有信息。
要求:
不使用数据库,速度越快越好 =>哈希表(散列)
代码实现如下:
新建一个Emp类(员工)
public class Emp {
int id;
String name;
Emp next;//存放下一个员工,以此形成链表形式,即Emp相当于Node
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
新建EmpLinkedList类 实现哈希表中的链表
public class EmpLinkedList {
private Emp head;
public void add(Emp emp){
if (head == null){
head = emp;
}else {
Emp curEmp = head;
while(curEmp.next != null){
curEmp = curEmp.next;
}
curEmp.next = emp;
}
}
public void list(int no){
if (head == null){
System.out.println("第 "+(no+1)+" 链表为空");
return;
}
System.out.print("第 "+(no+1)+" 链表的信息为: ");
Emp curEmp = head;
while (curEmp != null){
System.out.print("{id="+curEmp.id+" name="+curEmp.name+"} ==> ");
curEmp = curEmp.next;
}
System.out.println();
}
public Emp getById(int id) {
if (head == null) return null;
Emp curEmp = head;
while (curEmp != null){
if (curEmp.id == id){
return curEmp;
}
curEmp = curEmp.next;
}
return null;
}
public void delete(int id) {
if (head == null){
System.out.println("没有该员工,无法删除");
return;
}
Emp curEmp = head;
while (curEmp != null){
if (curEmp.next.id == id){
curEmp.next = curEmp.next.next;
System.out.println("已删除id为"+id+"的员工");
return;
}
}
System.out.println("没有该员工,无法删除");
}
}
新建HashTab类,实现存放Emp的哈希表
public class HashTab {
int size;
private EmpLinkedList[] empLinkedListArray;
public HashTab(int size) {
empLinkedListArray = new EmpLinkedList[size];
this.size = size;
//对列表数组进行初始化,不初始化会 NullPointerException
for (int i=0; i<size; i++){
empLinkedListArray[i] = new EmpLinkedList();
}
}
public void add(Emp emp){
//根据员工的id ,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将emp 添加到对应的链表中
empLinkedListArray[empLinkedListNO].add(emp);
}
public void list(){
for (int i=0; i<size; i++){
empLinkedListArray[i].list(i);
}
}
public void getById(int id){
int empLinkedListNO = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNO].getById(id);
if(emp != null) {//找到
System.out.printf("在第%d条链表中找到 雇员 id = %d\n", (empLinkedListNO + 1), id);
}else{
System.out.println("在哈希表中,没有找到该雇员~");
}
}
public void deleteById(int id){
int empLinkedListNO = hashFun(id);
empLinkedListArray[empLinkedListNO].delete(id);
}
//编写散列函数, 使用一个简单取模法
private int hashFun(int id){
return id % size;
}
}
测试代码
public class HashTabTest {
public static void main(String[] args) {
HashTab hashTab = new HashTab(10);
hashTab.add(new Emp(1,"Bob"));
hashTab.add(new Emp(2,"Jack"));
hashTab.add(new Emp(11,"Mark"));
hashTab.list();
hashTab.getById(11);
hashTab.deleteById(11);
System.out.println("=====================");
hashTab.list();
}
}
输出结果:
第 1 链表为空
第 2 链表的信息为: {id=1 name=Bob} ==> {id=11 name=Mark} ==>
第 3 链表的信息为: {id=2 name=Jack} ==>
第 4 链表为空
第 5 链表为空
第 6 链表为空
第 7 链表为空
第 8 链表为空
第 9 链表为空
第 10 链表为空
在第2条链表中找到 雇员 id = 11
已删除id为11的员工
=====================
第 1 链表为空
第 2 链表的信息为: {id=1 name=Bob} ==>
第 3 链表的信息为: {id=2 name=Jack} ==>
第 4 链表为空
第 5 链表为空
第 6 链表为空
第 7 链表为空
第 8 链表为空
第 9 链表为空
第 10 链表为空
3. java中典型的哈希表应用
如果对hash算法的原理不太明白,可以参考这篇文章 常见Hash算法原理
3.1 HashMap
实现原理
HashMap
基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
详细介绍链接:Java 集合系列10之 HashMap详细介绍
HashTable
和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。
详细介绍链接:Java 集合系列11之 Hashtable详细介绍
HashSet
HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
ConcurrentHashMap
位于juc(java.util.concurrent)包下,相比HashMap,它是线程安全的。
HashMap和HashTable区别
1)HashTable的方法前面都有synchronized来同步,是线程安全的;HashMap未经同步,是非线程安全的。
2)HashTable不允许null值(key和value都不可以) ;HashMap允许null值(key和value都可以)。
3)HashTable有一个contains(Object
value)功能和containsValue(Object
value)功能一样。
4)HashTable使用Enumeration进行遍历;HashMap使用Iterator进行遍历。
5)HashTable中hash数组默认大小是11,增加的方式是old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。
6)哈希值的使用不同,HashTable直接使用对象的hashCode; HashMap重新计算hash值,而且用与代替求模。