java实现哈希表

java实现哈希表

哈希表是一种数据结构,它可以快速的进行插入、查找、删除操作,无论数据量有多大,它都能把插入、查找和删除操作的时间复杂度降为O(1)级别。
哈希表是基于数组+链表进行实现的,数组创建后难以拓展,所以当哈希表中存储的数据过多时,会有很严重的性能下降。此时,我们可以通过扩展哈希表数组的长度来解决问题,但是扩展哈希表的长度是一个费时的过程。因此哈希表的设计人员必须根据实际的数据量来定义合适的哈希表数组的长度。

哈希表算法

如果我们知道一个数组的元素的索引,那么我们就可以通过索引在O(1)时间内获得元素。因此,如果我们能够将key对象与索引建立起一一对应的关系,那么我们就可以通过key对应的索引快速找到value值了,这个key对象转化为索引的算法就是哈希算法,这个索引叫做哈希码(散列码)。

基本数据类型的key值

  • 当key为int,short,byte,char类型时,可以简单的将它们转化为int类型。
  • 当key为long类型时,long型数据有64位,正好是int类型的两倍,我们可以执行异或操作将两部分结合,结果作为哈希码。这个过程称为折叠。
int hashCode = (int)(key^(key>>32));
//^是按位异或操作符,例如11110000^00000011得到11110011
  • 当key为float类型时,使用Float.floatToIntBits(key)的返回值作为哈希码,floatToIntBits()函数返回一个int值,该int值的二进制数与float数据的二进制表示相同。
  • 当key为double类型时,使用Double.doubleToLongBits方法转化为long值,然后再执行折叠操作,结果作为哈希码。
long bits = Double.doubleToLongBits(key);
int hashCode = (int)(bits^(bits>>32));

字符串类型的key值

当key位字符串类型时,我们有两种方法进行处理:
第一种方法比较直观,我们可以将所有字符串的Unicode码求和作为字符串的哈希码。(Unicode,也叫统一码、万国码、单一码,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。)该方案只考虑了key的各个字母的个数,并没有考虑到字母的顺序。因此遇到具有相同字母的key值,将产生很多冲突,例如eat和ate。
因此产生了第二种方法,来将字母的顺序考虑在内。具体的方法为:

java实现哈希表
这里的S0为s.charAt(i)。这个表达式被称为多项式哈希码。使用针对多项式求值的Horner规则,可以如下高效的计算该多项式:
java实现哈希表

这个计算对于较长的字符串来说会导致溢出,但是Java会忽略算术溢出。应该选择一个合适的b值来最小化哈希冲突。查阅资料可以知道,b的较好取值为31,33,37,39,41,String类中,hashCode()被重写为采用b=31的多项式哈希码。

压缩哈希码

我们通过哈希算法计算出来的哈希码可能是一个很大的整数,超出了哈希表数组索引的范围,所以我们需要将它缩小到合适的范围。假设哈希表的数组长度为N。将一个整数缩小到0~N-1的方法通常是:

index = hashCode % N;

理论情况下,为了保证索引均匀展开,应该为N选择一个素数。然而,选择一个大的素数将很耗时。Java API中java.util.HashMap的实现,N采用16作为初始值,每次reHash将N扩大一倍。这样做具有合理性。当N为2的整数幂时,可以通过&运算来压缩哈希码,位运算比乘、除、取余操作要快很多,可以提高哈希表的性能,如下所示:

index = hashCode & (N-1);

&是一个按位AND操作符,如果两个二进制数的相同位都为1,那么结果中的对应位也为1。例如,假如N=4,hashCode = 7,那么index = hashCode & (N-1) = 0111 & 0011 = 0011 转换成十进制就是3,结果和7%4是一样的。
为了保证哈希表中的元素是均匀分布的,java.util.HashMap中为主哈希函数增加了一个补充的哈希函数,该函数定义为:

private static void supplementalHash(int h){
    h ^= (h>>>20)^(h>>>12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

所以完整的哈希函数如下定义:

h(hashCode) = supplementalHash(hashCode) & (N-1);

补充的哈希函数避免了两个低位相同的数之间的冲突,比如10101000 & 00001111 和 11111000 & 00001111所得到的结果都是1000。使用补充的哈希函数将减少这种冲突。

自定义一个简单的哈希表

我们使用int类型作为key值,String类型作为value值,来实现

  • step1:完成MyHashMap数据结构
    我们在src源文件下新建一个包,命名为dataStructure,然后新建类MyHashMap,写入以下代码:
package dataStructure;

public class MyHashMap {
    Node[] memo ;
    int count ; //哈希表中元素个数;
    /*
        * 无参构造方法
        * 此处将MyHashMap默认的初始容量为4,并没有设置最大容量,所以当遇到整数溢出/内存不够时,会出错。
        */
    public MyHashMap() {
        count = 0;
        memo = new Node[4];
    }

    /*
    * 添加一个新的对,到哈希表中。
    * int类型的数据通过哈希函数还是它本身,所以就直接用int值作为哈希码。
    * 这里压缩哈希码使用了取模运算,此算法并不高效。当哈希表长度为2的整数次幂时,可以使用 哈希值 &(数组长度-1)来压缩哈希码。
    * 当多个key值算出的索引相同时,使用链表来处理哈希冲突,将新插入的节点作为链表的头节点。
    */
    public void put(int k , String v) {
        int index = k % memo.length;
        Node node = new Node(k,v);
        node.next = memo[index];
        memo[index ] = node;
        count++;
                //当元素数目为哈希表大小的2倍时,增大哈希表的大小。
        if(count>memo.length*2) {
             reHash();
        }
    }

    /*
    * 返回索引为k的value值
    *
    */
    public String get(int k) {
        int index = k % memo.length;
        Node node = memo[index];
        while(node!=null) {
            if(node.data.k == k) {
                return node.data.v;
            }
            node = node.next;
        }
        return null;

    }
    /*
    * reHash方法:将哈希表的数组长度扩展为原来的2倍
    * 新建一个长度为原来2倍的新数组,遍历老数组,将老数组中的所有节点逐个插入到新数组中。
    */
    private void reHash() {
        Node[] newMemo = new Node[memo.length*2];
        for (int i = 0; i < memo.length; i++) {
            Node node = memo[i];
            while(node!=null) {
                int index = node.data.k % newMemo.length;
                Node tNode = new Node(node.data);
                tNode.next = newMemo[index];
                newMemo[index ] = tNode;
                node = node.next;
            }
        }
        memo = newMemo;

    }

    //返回哈希表中元素的个数
    public int size() {
        return count;
    }

    /*
    * remove方法,将key=k的元素移出哈希表
    * 计算出索引,遍历索引处的链表,逐个比对key值,找到相同的值时返回value值
    */
    public boolean remove(int k) {
        int index = k % memo.length;
        Node node = memo[index];
        if(node.data.k==k) { //头节点为要移除的节点
            if(memo[index].next!=null) {
                memo[index] = memo[index].next;
            }else {
                memo[index] = null;
            }
            count--;
            return true;

        }
        Node preNode = node;
        node = node.next;
        while(node!=null) {
            if(node.data.k == k) {
                 preNode.next = node.next;
                 count--;
                 return true;
            }
            node = node.next;
        }
        return false;

    }
    //返回哈希表中数组的长度
    public int getLength() {
        return memo.length;
    }

}

class Node{
    Data data;
    Node next;
    public Node(Data data) {
        this.data = data;
    }
    public Node(int k , String v) {
        Data data = new Data(k, v);
        this.data=data;
    }
    public Data getData() {
        return data;
    }
}
/**
 * key值为int类型
 * value为String
 */
class Data{
    int k;
    String v;
    public Data(int k,String v) {
        this.k = k ;
        this.v = v;
    }

}

  • step2:测试MyHashMap
    在src下新建package,命名为testHash,再新建类TsetMyHash,写入以下代码:
package test;

import dataStructure.MyHashMap;

public class TsetMyHash {
    public static void main(String[] args) {
        MyHashMap myHashMap = new MyHashMap();
        myHashMap.put(1,"value1");
        myHashMap.put(2,"value2");
        myHashMap.put(3,"value3");
        myHashMap.put(4,"value4");
        myHashMap.put(5,"value5");
        myHashMap.put(6,"value6");
        myHashMap.put(7,"value7");
        myHashMap.put(8,"value8");
        myHashMap.put(9,"value9");
        myHashMap.put(10,"value10");

        System.out.println("key为6,对应的value值为:"+ myHashMap.get(6));
        boolean flag = myHashMap.remove(6);
        System.out.println("remove(6)的执行结果为: "+flag );
        System.out.println("哈希表的长度为: " + myHashMap.getLength());
        System.out.println("哈希表存储的元素个数为: " + myHashMap.size());
    }
}

执行结果如下:

java实现哈希表

Original: https://www.cnblogs.com/classicltl/p/16535722.html
Author: classic123
Title: java实现哈希表

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/570175/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【Java面试手册-基础篇】Java中的main()方法能否被重载?

    答案是肯定的,我们可以重载main()方法,一个Java类可以有任意数量的main()方法,比如下面的 MainDemo 类,就有多个 main() 方法。 package com…

    Java 2023年6月8日
    093
  • 2022保研经历-有删减

    2022 保研经历 我也知道大家仅仅是想看题目而已。 恕我直言,那些 排版混乱 ,看起来讲了很多,实际上既没有提供面试题目,也没有提供备考经验,反而只感动自己、像记流水账、对别人没…

    Java 2023年6月7日
    062
  • Maven选择指定模块编译

    选择单个子模块进行打包 mvn -U -pl MODULE-NAME -am clean install MODULE-NAME: 模块名称,若路径不在根目录中,请填写全量路径,如…

    Java 2023年6月8日
    062
  • 微服务 mybatis-plus 添加日志输出

    spring配置 spring: redis: database: 6 host: 192.168.8.248 port: 6379 password: datasource: d…

    Java 2023年5月30日
    076
  • 管理敏感词+图片识别文字审核敏感词

    1.DFA实现原理 DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。 存储:一次性的把所有的敏感词存储到了多个map中,就是下图表示这…

    Java 2023年6月9日
    091
  • LeetCode.1089-重复的0(Duplicate Zeros)

    这是小川的第 392次更新,第 423篇原创 今天介绍的是 LeetCode算法题中 Easy级别的第 255题(顺位题号是 1089)。给定一个固定长度的整数数组 arr,复制每…

    Java 2023年6月5日
    077
  • 鸿蒙(HarmonyOS)开发笔记二:使用DevEco Studio创建一个项目

    ,在对harmonyOS有了一个初步认知之后,我们使用DevEco Studio来创建一个项目,把项目运行起来,先从整体上来了解一下harmonyOS项目的整体结构以及开发工具的基…

    Java 2023年6月16日
    0102
  • 【年度钻石】Linux云计算+运维(1)《博学谷》黑马

    Java互联网企业架构技术VIP课程【腾讯课堂每特】 Java互联网企业架构技术VIP课程【腾讯课堂每特】 课程 内容 站在架构角度,基于装饰模式纯手写设计多级缓存框架 本节课需要…

    Java 2023年6月7日
    071
  • 设计模式之解释器模式

    解释器模式属于行为型模式;指给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达…

    Java 2023年6月5日
    072
  • 分页数据展示后台代码和前台代码

    类别id传递 点击了不同的分类后将来看到的旅游线路不一样的。通过分析数据库表结构,发现 旅游线路表和分类表是一个多对一的关系 CategoryServiceImpl实现类: pub…

    Java 2023年6月6日
    090
  • SpringCloud+Alibaba微服务教程,Java自学/进阶程序员必看

    正文 Spring Cloud是目前市面上最火爆的Java微服务技术栈,因其功能丰富涉及微服务管理全面,并且在高可靠、高可阔以及在应对复杂业务和承受并发的能力上发挥出色,使其受到众…

    Java 2023年6月9日
    078
  • 利用订阅模式实现缓存更新

    1. 引言 很多Web项目,都需要和数据库打交道,典型的就是CRUD(读,写,更新,删除)操作。无论是哪种数据库,Asp.Net MVC 作为后端框架的项目,都有很多操作数据库的类…

    Java 2023年6月5日
    055
  • JDBC

    概念:JDBC即Java DataBase Connectivity,Java数据库连接,Java语言操作数据库 本质:是官方(sun公司)定义的一套所有关系型数据库的规则,即接口…

    Java 2023年6月6日
    097
  • Spring Boot:整合MyBatis框架

    综合概述 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBa…

    Java 2023年5月30日
    099
  • C#创建一个线程为什么会消耗那么多的内存?

    最近开始使用java。 无意中发现在java里面创建一个线程只需要大概几十K的内存,但是在C#里面创建一个线程却需要占用1M左右的内存。 这一点上C#让我感到比较失望。。为什么会有…

    Java 2023年5月29日
    070
  • AWS Simple Email Service(SES)邮件发送服务-功能调研

    面向国内用户,发短信或者通知推送居多,发邮件这个功能用的不多,主要还是海外欧美用户比较流行,刚好公司要用,写一篇AWS SES功能调研文章讲解一下,跨境电商的同行可以参考一下。废话…

    Java 2023年6月13日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球