Java模拟哈希表并简单的使用
基本介绍
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
可见,哈希表/散列表这种数据结构的特点:是一个一维数组,这个数组中的每个元素是一个单向链表;(数组和链表的结合体);数组:在查询方面效率高,但是在随机增删方面效率很低;单向链表:在随机增删方面效率较高,但是在查询方面效率很低;哈希表/散列表就是将以上两种数据结构融合在一起!!!
散列分布均匀的概念
假设有100个元素,其最好能划分为10个单向链表,每个单向链表上有10个元素,这种情况是最好的,是散列分布均匀的;
哈希碰撞的概念
如果两个对象a1和a2的哈希值相同,这两个一定是在同一个链表上的(哈希值会经过哈希算法/哈希函数 转换成数组下标,对应的就是某个链表);注意:当a1和a2的哈希值不同,这两个任然有可能在同一个链表上,因为两个不同的哈希值经过哈希算法/哈希函数 有可能会转换为同一个数组下标(这就是 “哈希碰撞”);
简单应用
假设有许多的用户,如何将这些用户信息使用哈希表保存起来,并且能够实时查询、修改、删除、或者添加新的用户?数据类User
的创建
package edu.hebeu.hashtable;
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
存放数据的节点类Node
package edu.hebeu.hashtable;
public class Node {
public Node next;
public User data;
public Node(User data) {
this.data = data;
}
}
单链表类LinkedList
的创建(这里来个不带头节点的,因为我前面写的都是带头节点的)
package edu.hebeu.hashtable;
public class LinkedList {
private Node head;
/**
* 尾插法
* @param node
*/
public void tailInsert(Node node) {
if (head == null) {
head = node;
return;
}
if(this.select(node) != null) {
System.err.println("已存在id为" + node.data.getId() + "的用户,添加失败!");
return;
}
Node lastNode = head;
while(true) {
if(lastNode.next == null) {
break;
}
lastNode = lastNode.next;
}
lastNode.next = node;
}
/**
* 删除节点
* @param node
*/
public void del(Node node) {
if (head == null) {
return;
}
if(head.data.getId() == node.data.getId()) { // 如果删除的元素为链表的第一个节点
head = head.next;
return;
}
Node preNode = head;
while (true) {
if (preNode.next.data.getId() == node.data.getId()) {
preNode.next = preNode.next.next;
break;
}
if (preNode.next == null) {
break;
}
preNode = preNode.next;
}
}
/**
* 修改节点
* @param node
*/
public void update(Node node) {
if (head == null) {
return;
}
Node currentNode = head;
while (true) {
if (currentNode == null) {
break;
}
if (currentNode.data.getId() == node.data.getId()) {
currentNode.data = node.data;
break;
}
currentNode = currentNode.next;
}
}
/**
* 查询节点
* @param node
* @return
*/
public Node select(Node node) {
if(head == null) {
return null;
}
Node currentNode = head;
while(true) {
if (currentNode == null) {
break;
}
if (currentNode.data.getId() == node.data.getId()) {
return currentNode;
}
currentNode = currentNode.next;
}
return null;
}
/**
* 打印链表
*/
public void show() {
if (head == null) {
return;
}
Node currentNode = head;
while (true) {
if(currentNode == null) {
break;
}
System.out.print(currentNode.data + " -> ");
currentNode = currentNode.next;
}
}
}
哈希表类HashTable
的创建,这里我们用最简单的通过User类的id取模法来决定改对象放到哪个链表
package edu.hebeu.hashtable;
/**
* 哈希表
* @author 13651
*
*/
public class MyHashTable {
/**
* 哈希表的每一条链表,即数组每个元素对应的链表
*/
private LinkedList[] linLists;
/**
* 哈希表的大小,即数组长度
*/
private int size;
public MyHashTable(int size) {
this.size = size;
linLists = new LinkedList[size];
for (int i = 0; i < size; i++) {
linLists[i] = new LinkedList();
}
}
/**
* 添加用户的方法
* @param user
*/
public void add(User user) {
int linListNum = hashFun(user.getId()); // 通过添加的用户的id 利用散列函数 获得应该将这个用户放在哪个链表上
linLists[linListNum].tailInsert(new Node(user)); // 将该用户加入到指定的链表下
}
/**
* 删除用户的方法
* @param id
*/
public void delete(User user) {
int linListNum = hashFun(user.getId()); // 通过id判断该用户应该在哪个链表出现
linLists[linListNum].del(new Node(user)); // 进行删除操作
}
/**
* 修改用户的方法
* @param user
*/
public void update(User user) {
int linListNum = hashFun(user.getId()); // 通过id判断该用户应该在哪个链表出现
linLists[linListNum].update(new Node(user)); // 进行修改操作
}
/**
* 查询用户的方法
* @param user
* @return
*/
public User select(User user) {
int linListNum = hashFun(user.getId()); // 通过id判断该用户应该在哪个链表出现
Node node = linLists[linListNum].select(new Node(user));
if(node == null) return null;
return node.data;
}
/**
* 打印哈希表(所有的链表)
*/
public void show() {
for (int i = 0; i < size; i++) {
System.out.print("链表" + (i + 1) + ":"); linLists[i].show();
System.out.println();
}
}
/**
* 散列函数,通过取模法
* @param id
* @return
*/
public int hashFun(int id) {
return id % size;
}
}
测试类Test
的创建
package edu.hebeu;
import java.util.Random;
import java.util.Scanner;
import edu.hebeu.hashtable.MyHashTable;
import edu.hebeu.hashtable.User;
public class Test {
private static Scanner SCANNER = new Scanner(System.in);
private static MyHashTable HASH_TABLE = new MyHashTable(9); // 创建包含6条链表的哈希表
public static void main(String[] args) {
menu();
}
private static void menu() {
int id;
String name;
User user;
boolean isContinue = true;
while (isContinue) {
System.out.println();
System.out.println("添加用户a(add)");
System.out.println("删除用户d(delete)");
System.out.println("修改用户u(update)");
System.out.println("查询用户s(select)");
System.out.println("展示用户sa(showAll)");
System.out.println("插入10条用户at(addTen)");
System.out.println("退出e(exit)");
System.out.println("请选择:"); String keyword = SCANNER.next();
switch (keyword) {
case "a":
System.out.println("请输入添加用户的id:"); id = SCANNER.nextInt();
System.out.println("请输入添加用户的name:"); name = SCANNER.next();
user = new User();
user.setId(id);
user.setName(name);
HASH_TABLE.add(user);
break;
case "d":
System.out.println("请输入删除的用户id:"); id = SCANNER.nextInt();
user = new User();
user.setId(id);
HASH_TABLE.delete(user);
break;
case "u":
System.out.println("请输入修改的用户的id:"); id = SCANNER.nextInt();
System.out.println("请输入修改的用户的name:"); name = SCANNER.next();
user = new User();
user.setId(id);
user.setName(name);
HASH_TABLE.update(user);
break;
case "s":
System.out.println("请输入查询用户的id:"); id = SCANNER.nextInt();
user = new User();
user.setId(id);
System.out.println(HASH_TABLE.select(user) == null ? "未找到" + id + "的用户" : HASH_TABLE.select(user));
break;
case "sa":
HASH_TABLE.show();
break;
case "at":
for (int i = 0; i < 10; i++) {
user = new User();
id = new Random().nextInt(1000);
name = "user" + id;
user.setId(id);
user.setName(name);
HASH_TABLE.add(user);
}
System.out.println("插入10条数据成功");
break;
case "e":
isContinue = false;
break;
default:
break;
}
}
}
}
测试