哈希表也称散列表,是由数组+单链表构成的一种数据结构,它是根据关键值(key-value)进行对数据的访问的,它通过把关键码值映射在表中的一个位置来访问记录,以加快查找速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
其底层如图:
所以,由此可见我们的哈希表实质就是一个链表的数组,即linkedList[] hashtable 。
对于创建哈希表,我们首先需要创建一个单链表,并且创建一个类,作为链表中添加的数据。
这里我就创建了一个Emp类,属性有id,name,next。作为链表中的添加元素。
代码如下:
//创建添加元素
static class Emp {
int id;
String name;
Emp next;
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
public Emp() {
}
@Override
public String toString() {
return "emp{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
然后创建一个单链表,实现CRUD操作,若对创建单链表的朋友可以看我以前的文章,有对创建单链表的介绍与实现。
//创建一个链表
static class LinkedList {
Emp head;
/**
* 添加emp到链表中
* @param emp
*/
public void addEmp(Emp emp) {
if (isEmpty()) {
//如果为空,直接将雇员添加到链表中
head = emp;
System.out.println("添加成功");
} else {
//不为空,根据no值进行插入
Emp temp = head;//借助辅助结点
while(temp.next!=null&&temp.next.id<emp.id){
//当下一个结点不为空而且下一个结点的id值小于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id>emp.id
if(temp.next==null){
//直接插入在后面
temp.next=emp;
System.out.println("添加成功");
}else{
//此时为temp.next.id>emp.id,即找到了插入数据的前一个结点
emp.next=temp.next;
temp.next=emp;
System.out.println("添加成功");
}
}
}
/**
* 删除数据
* @param id 要删除的数据的id值
*/
public void deleteEmp(int id){
if (isEmpty()){
//如果链表为空,无法删除
System.out.println("链表为空,无法删除");
}else{
if(head.id==id){//要删除的结点为头结点
//将后面的结点前移
head=head.next;
System.out.println("删除成功");
return;//结束方法
}
Emp temp = head;//借助辅助结点
while (temp.next!=null&&temp.next.id!=id){
//当下一个结点不为空而且下一个结点的id值不等于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id==emp.id
if(temp.next==null){
//遍历结束,没有对应的员工
System.out.println("没有该员工,无法删除");
}else{
//此时为temp.next.id==emp.id,即找到了要被删除结点的前一个结点
temp.next=temp.next.next;
System.out.println("删除成功");
}
}
}
/**
* 更改结点
* @param emp 要更改的结点
*/
public void update(Emp emp){
if (isEmpty()){
//如果链表为空,无法删除
System.out.println("链表为空,没有数据更改");
}else{
Emp temp = head;//借助辅助结点
if(head.id==emp.id){
//更改的是头结点
emp.next=head.next;
head=emp;
System.out.println("更改成功");
return;
}
while (temp.next!=null&&temp.next.id!=emp.id){
//当下一个结点不为空而且下一个结点的id值不等于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id==emp.id
if(temp.next==null){
//遍历结束,没有对应的员工
System.out.println("没有该员工,无法更新");
}else{
//此时为temp.next.id==emp.id,即找到了要被更新结点的前一个结点
emp.next=temp.next.next;
temp.next=emp;
System.out.println("更改成功");
}
}
}
/**
* 查询员工
* @param id 员工的id
* @return 返回员工
*/
public Emp selectEmp(int id){
if (isEmpty()){
//如果链表为空,无法删除
throw new RuntimeException("链表为空,没有数据");
}else{
Emp temp = head;//借助辅助结点
while (temp!=null&&temp.id!=id){
//当当前结点不为空而且当前结点的id值不等于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id==emp.id
if(temp.next==null){
//遍历结束,没有对应的员工
throw new RuntimeException("没有该数据");
}else{
//此时为找到该员工,返回该员工
return temp;
}
}
}
/**
* 遍历链表的信息
*/
public void show(){
if (isEmpty()){
System.out.println("链表为空");
}else {
Emp temp = head;
while(temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
/**
* 判断链表是否为空
*
* @return 如果为空,返回true。否则返回false
*/
public boolean isEmpty() {
return head == null;
}
}
这些前提准备完成后,我们就进入创建哈希表的操作,我们刚才指出,哈希表就是一个链表的集合,那么要操作这些链表,我们就得需要创建一个链表数组。即linkedList[] hashtable
然后还有一个要点:就是我们加入一个数据时,应该添加到哪一个链表中呢?这里我就通过一个对加入数据的id值的一个简单的取模,就可以判断出该数据应该添加到什么位置。
比如说:我们传入的Emp的id属性为8,我们的哈希表的链表数组长度为8,id/size=8/8=0,这时就把该数据加入到hashtable[0]中,这就成功添加数据了。
大家可以尝试着实现,我把完整代码附上,里面注释解释的较为清楚:
package com.liu.hashtable;
/**
* @author liuweixin
* @create 2021-09-12 15:02
*/
//哈希表的实现
public class HashTab {
//因为哈希表实质为一个单链表数组
private LinkedList[] lists;
private int maxSize;
public HashTab(int maxSize) {
this.maxSize = maxSize;
lists=new LinkedList[this.maxSize];
for (int i = 0; i < maxSize; i++) {
//初始化lists单链表数组
lists[i]=new LinkedList();
}
}
public static void main(String[] args) {
HashTab hashTab = new HashTab(10);
hashTab.add(new Emp(1,"a"));
hashTab.add(new Emp(2,"b"));
hashTab.add(new Emp(4,"c"));
hashTab.add(new Emp(3,"d"));
hashTab.add(new Emp(11,"e"));
hashTab.show();
hashTab.delete(3);
hashTab.show();
hashTab.update(new Emp(6,"sb"));
hashTab.show();
System.out.println(hashTab.select(4));
}
/**
* 判断传进来的员工id应该放在那个单链表中
* @param id 传进来的员工id
* @return
*/
public int check(int id){
//来判断其应该放在哪个链表
return id%maxSize;
}
/**
* 添加员工
* @param emp 添加的员工的信息
*/
public void add(Emp emp){
//判断应该放置在哪个链表中
int check = check(emp.id);
//添加员工
lists[check].addEmp(emp);
}
/**
* 删除员工
* @param id 删除的员工的id
*/
public void delete(int id){
//判断应该放置在哪个链表中
int check = check(id);
//添加员工
lists[check].deleteEmp(id);
}
/**
* 更改员工
* @param emp 更改的员工信息
*/
public void update(Emp emp){
//判断应该放置在哪个链表中
int check = check(emp.id);
//删除员工
lists[check].update(emp);
}
/**
* 查找员工
* @param id 员工的id
* @return 返回查询到的员工
*/
public Emp select(int id){
//判断应该放置在哪个链表中
int check = check(id);
//找寻员工
return lists[check].selectEmp(id);
}
/**
* 遍历链表中的信息
*/
public void show(){
for (int i = 0; i < lists.length; i++) {
if(!lists[i].isEmpty()){
System.out.print("第"+i+"个链表的信息为:");
lists[i].show();
}
}
}
//创建一个链表
static class LinkedList {
Emp head;
/**
* 添加emp到链表中
* @param emp
*/
public void addEmp(Emp emp) {
if (isEmpty()) {
//如果为空,直接将雇员添加到链表中
head = emp;
System.out.println("添加成功");
} else {
//不为空,根据no值进行插入
Emp temp = head;//借助辅助结点
while(temp.next!=null&&temp.next.id<emp.id){
//当下一个结点不为空而且下一个结点的id值小于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id>emp.id
if(temp.next==null){
//直接插入在后面
temp.next=emp;
System.out.println("添加成功");
}else{
//此时为temp.next.id>emp.id,即找到了插入数据的前一个结点
emp.next=temp.next;
temp.next=emp;
System.out.println("添加成功");
}
}
}
/**
* 删除数据
* @param id 要删除的数据的id值
*/
public void deleteEmp(int id){
if (isEmpty()){
//如果链表为空,无法删除
System.out.println("链表为空,无法删除");
}else{
if(head.id==id){//要删除的结点为头结点
//将后面的结点前移
head=head.next;
System.out.println("删除成功");
return;//结束方法
}
Emp temp = head;//借助辅助结点
while (temp.next!=null&&temp.next.id!=id){
//当下一个结点不为空而且下一个结点的id值不等于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id==emp.id
if(temp.next==null){
//遍历结束,没有对应的员工
System.out.println("没有该员工,无法删除");
}else{
//此时为temp.next.id==emp.id,即找到了要被删除结点的前一个结点
temp.next=temp.next.next;
System.out.println("删除成功");
}
}
}
/**
* 更改结点
* @param emp 要更改的结点
*/
public void update(Emp emp){
if (isEmpty()){
//如果链表为空,无法删除
System.out.println("链表为空,没有数据更改");
}else{
Emp temp = head;//借助辅助结点
if(head.id==emp.id){
//更改的是头结点
emp.next=head.next;
head=emp;
System.out.println("更改成功");
return;
}
while (temp.next!=null&&temp.next.id!=emp.id){
//当下一个结点不为空而且下一个结点的id值不等于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id==emp.id
if(temp.next==null){
//遍历结束,没有对应的员工
System.out.println("没有该员工,无法更新");
}else{
//此时为temp.next.id==emp.id,即找到了要被更新结点的前一个结点
emp.next=temp.next.next;
temp.next=emp;
System.out.println("更改成功");
}
}
}
/**
* 查询员工
* @param id 员工的id
* @return 返回员工
*/
public Emp selectEmp(int id){
if (isEmpty()){
//如果链表为空,无法删除
throw new RuntimeException("链表为空,没有数据");
}else{
Emp temp = head;//借助辅助结点
while (temp!=null&&temp.id!=id){
//当当前结点不为空而且当前结点的id值不等于传入的emp的id值,则继续循环
temp=temp.next;
}
//当循环结束,要么temp.next为空,要么temp.next.id==emp.id
if(temp.next==null){
//遍历结束,没有对应的员工
throw new RuntimeException("没有该数据");
}else{
//此时为找到该员工,返回该员工
return temp;
}
}
}
/**
* 遍历链表的信息
*/
public void show(){
if (isEmpty()){
System.out.println("链表为空");
}else {
Emp temp = head;
while(temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
/**
* 判断链表是否为空
*
* @return 如果为空,返回true。否则返回false
*/
public boolean isEmpty() {
return head == null;
}
}
//创建添加元素
static class Emp {
int id;
String name;
Emp next;
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
public Emp() {
}
@Override
public String toString() {
return "emp{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
}