本篇博客是根据b站尚硅谷的数据结构教程,学习后写的学习笔记,本篇博客的重点在自己编写的代码注释上

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过映射函数把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做哈希表(散列表)

哈希表一般是数组+链表或者数组+二叉树,加上哈希表的主要目的是提高查询速度

有一个长表如何建立一个hash索引分几个小表 哈希表和索引表_哈希表


像上面这张图,左边的一侧是数组,即散列表,里面存放的不再是数据,而是一个一个的链表,然后链表里面再存储数据。比如有一个数字 16 ,散列函数是 % 15,因此用16%15=1,映射到散列表索引为1的位置,然后到这个位置对应的链表里面查询数据。这样查询速度就提升了很多倍,不需要一个一个链表的查询。现在有一个需求:有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,姓名,住址等),当输入该员工的id时,要求查找到该员工的所有信息

有一个长表如何建立一个hash索引分几个小表 哈希表和索引表_哈希表_02


这里的hashtable是一个类,里面有一个数组,数组里面存放着链表类EmpLinkedList对象,即一条一条的单链表。链表类里面管理着节点类Emp对象,一个节点就是一个雇员的全部信息。这里的链表没有头节点,链表的第一个节点就存放雇员信息

这样不同的雇员,根据不同的id,经过散列函数映射到数组上的某一个位置,这个数组位置上有一条单链表,然后插入到链表尾部。这样就将不同的雇员信息散列在了不同的单链表里面,不会都存放在一条单链表上,提高了查询速度。

下面是具体的代码

首先是节点类,Emp,用来存放一个员工的信息
这里是单链表里面的节点,因此只需要一个next指向下一个节点即可

//定义一个节点的类,表示一个雇员
class Emp{
    public int id;
    public String name;
    //单链表,next指针
    public Emp next; //next默认为null
    public Emp(int id,String name){
        this.id = id;
        this.name = name;
    }
}

然后是单链表类,用来存放多个节点,管理多个雇员的信息

//创建EmpLinkedList,表示链表
class EmpLinkedList{
    //这里链表没有头节点,因此head头指针直接指向链表的第一个数据节点
    private Emp head;  //默认为null
}

往单链表类里面添加不同的方法,用来管理雇员的信息

//添加雇员到链表
    //这里假定添加雇员的时候,id是自增长的,即id是按从小到大的顺序进入链表
    //因此直接将emp雇员添加到链表的最后面
    public void add(Emp emp){
        //如果head为null,说明此时链表为空,让head指向第一个雇员emp
        if(head == null){
            head = emp;
            return;
        }
        //辅助变量temp,类似于指针,用来遍历整个链表
        Emp temp = head;
        while(true){
            //temp.next为null,说明temp已经是链表的最后一个节点
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //退出循环时,temp这个变量存放的内容就是链表的最后一个节点对象
        //所以temp.next其实是给temp存放的当前节点对象里面的next变量赋值
        //将emp添加到链表的最后面
        temp.next = emp;
    }

    //遍历链表的全部节点
    public void list(int no){
        if(head == null){
            System.out.println("第 "+no+" 条链表为空");
            return;
        }
        System.out.print("第 "+no+" 条链表的信息为");
        Emp temp = head;
        while(true){
            System.out.printf("=> id=%d name=%s\t",temp.id,temp.name);
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        System.out.println();
    }


    //根据id查找雇员
    //如果查找到,就返回emp,如果没有找到,就返回null
    public Emp getEmpById(int id){
        //判断链表是否为空
        if(head == null){
            System.out.println("当前链表为空");
            return null;
        }
        Emp temp = head;
        while(true){
            if(temp.id == id){
                //找到正确雇员,结束循环
                break;
            }
            if(temp.next == null){
                //temp已经是最后一个节点,id依旧不一样,则置为null,结束循环
                temp = null;
                break;
            }
            temp = temp.next;
        }
        //根据循环的结果,返回雇员或者null
        return temp;
    }


    //删除雇员
    public void del(int id){
        if(head == null){
            System.out.println("链表为空,无法删除");
            return;
        }
        Emp temp = head;
        int flag = 0;
        while(true){
            if(head.id == id){
                flag = 0;
                break;
            }
            if(temp.next == null){
                flag = 1;
                break;
            }
            if(temp.next.id == id){
                flag = 2;
                break;
            }
            temp = temp.next;
        }
        if(flag == 0){
            System.out.println("删除第一个节点");
            head = head.next;
        }else if(flag == 1){
            System.out.println("查找的雇员id不存在");
        }else{
            System.out.println("删除当前节点");
            temp.next = temp.next.next;
        }

    }

之后是HashTab类,用来创建一个hashtable对象,这个对象以数组的形式存储多个单链表

//创建HashTable数组,用来管理多条链表
class HashTab{
    //HashTable是一个存储着多条链表的数组
    //EmpLinkedList[]类似于int[],用来指明数组里面元素的数据类型
    private EmpLinkedList[] empLinkedListArray;
    private int size; //表示一共有多少条链表

	public HashTab(int size){
        this.size = size;
        //初始化empLinkedListArray数组
        empLinkedListArray = new EmpLinkedList[size];
        //上面那一步只是初始化了一个hashtable,但是hashtable里面的链表都没有初始化
        //也就是说当前只有hashTable这一个数组,数组里面全都是null,没有链表
        //因此要对所有的链表进行初始化操作
        for(int i=0;i<size;i++){
            empLinkedListArray[i] = new EmpLinkedList();
        }
    }

	//定义一个散列函数
    public int hashFun(int id){
        return id % size;
    }
}

初始化HashTable和单链表后,需要在HashTab类里面增加一些方法,用来管理单链表

//外界查询和增删数据只会看见HashTab,看不见链表
    //用HashTab里面的方法来管理链表
    //添加雇员
    public void add(Emp emp){
        //根据员工的id,在hashTable上查找,得到该员工应当添加到哪条链表
        int empLinkedListId = hashFun(emp.id);
        //将emp添加到对应的链表里面
        //empLinkedListArray[empLinkedListId]就是hashTab中存储的某一条链表
        empLinkedListArray[empLinkedListId].add(emp);
    }

    //遍历所有的链表
    public void list(){
        for(int i=0;i<size;i++){
            empLinkedListArray[i].list(i);
        }
    }


    //根据输入的id,查找雇员
    public void getEmpById(int id){
        //根据散列函数判断去哪条链表查找
        int empLinkedListNo = hashFun(id);
        Emp emp = empLinkedListArray[empLinkedListNo].getEmpById(id);
        if(emp != null){
            System.out.printf("在第%d条链表中找到雇员,id=%d\n",empLinkedListNo,id);
        }else{
            System.out.println("在哈希表中没有找到该雇员");
        }
    }


    //删除雇员
    public void del(int id){
        int empLinkedListNo = hashFun(id);
        empLinkedListArray[empLinkedListNo].del(id);
    }

最后在main方法中写一个菜单,测试一下

public static void main(String[] args) {
        //创建一个HashTable
        HashTab hashTab = new HashTab(7);
        //写一个简单的菜单
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.println("add:添加雇员");
            System.out.println("list:显示雇员");
            System.out.println("find:查找雇员");
            System.out.println("del:删除雇员");
            System.out.println("exit:退出系统");
            key = scanner.next();
            switch (key){
                case "add":
                    System.out.println("输入id");
                    int id = scanner.nextInt();
                    System.out.println("输入name");
                    String name = scanner.next();
                    //创建雇员
                    Emp emp = new Emp(id, name);
                    hashTab.add(emp);
                    break;
                case "list":
                    hashTab.list();
                    break;
                case "find":
                    System.out.println("输入id");
                    id = scanner.nextInt();
                    hashTab.getEmpById(id);
                    break;
                case "del":
                    System.out.println("输入id");
                    id = scanner.nextInt();
                    hashTab.del(id);
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                default:
                    break;
            }
        }
    }