【数据结构与算法】手写HashMap的模拟实现

news2024/11/16 11:37:22

✨哈喽,进来的小伙伴们,你们好耶!✨

🛰️🛰️系列专栏:【数据结构与算法】

✈️✈️本篇内容:手写HashMap的模拟实现!

🚀🚀代码存放仓库gitee:Java数据结构代码存放!

⛵⛵作者简介:一名双非本科大三在读的科班Java编程小白,道阻且长,你我同行

🚋🚋给大家推荐一个超级好用的刷题网站—牛客网点击链接注册,开启刷题之路!

85cb934670274cef935e78f6f6bb4904.png

 一、手写HashMap

🎁1、Map的常用方法

67be11e8c0624fe4b6707e2071f60acc.png

🍊方法一(基本类型)

因为map的底层结构是数组,每个元素是一个链表,所以我们这里定义的数组是Node类型,定义一个usedSize代表数组中有多少个数据。

 🍅1、内部类准备

  static class Node {
        public int key;
        public int val;
        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    public Node[] array;
    public int usedSize;

    public HashBuck() {
        array = new Node[8];
    }

🍎2、实现put以及头插法

首先假如我们要插入一个节点,那么我们首先需要找到这个节点,找到下标index,那么这里就涉及到头插和尾插法,JDK1.7默认是头插法,JDK1.8默认是尾插法,那么我们这里使用头插法来演示。

  public void put(int key,int val) {
        int index = key % array.length;
        //遍历Index下标的数组,如果有相同的key那么替换
        Node cur = array[index];
        while (cur != null) {
            if(cur.key == key) {
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        //进行头插法
        Node node = new Node(key, val);
        node.next = array[index];
        array[index] = node;
        usedSize++;
        if( loadFactor() >= 0.75f) {
            resize();
        }
    }

 🍏3、扩容函数resize

这里扩容不能仅仅将数组增大到二倍,因为扩容之后对应的节点下标会发生变化,所以这里需要重新哈希!需要注意的是我们将节点插入到新的下标之后,那么我们需要定义一个curNext来提前存储cur.next,不然当我们插入之后,链表就会断掉,找不到cur.next了。

   private void resize() {
        Node[] newArray = new Node[2*array.length];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null) {
                Node curNext = cur.next;
                int newIndex = cur.key % newArray.length;
                //拿着cur节点 进行插入到新的位置
                cur.next = newArray[newIndex];
                newArray[newIndex] = cur;
                cur =  curNext;
            }
        }
        array = newArray;
    }

🍋实验: 这里我们给出main函数代码,来测试一下我们的resize()函数是否有问题。

   public static void main(String[] args) {
        HashBuck hashBuck = new HashBuck();
        hashBuck.put(1,6);
        hashBuck.put(2,7);
        hashBuck.put(3,8);
        hashBuck.put(4,54);
        hashBuck.put(5,66);
        hashBuck.put(6,234);
        hashBuck.put(15,888);
        System.out.println(hashBuck.get(6));
    }

🍯这里我们给出的数组空间大小是8,那么已知负载因子是0.75,所以我们在第六个元素的位置打个断点, 来debug一下。

ce60453aeecd4452b192c068e655cc1c.png

🍻 我们发现,元素的存储没有问题,那么点击上面那个蓝色往右下角偏移的箭头,代码会向下执行。

f500e568a77840ba8a82026b0d5dc449.png

🍺OK,因为这里的15 % 16 = 15;所以15应该插在index为15的位置,usedsize = 7,没有问题。

🥞4、实现get方法

    public int get(int key) {
        int index = key % array.length;
        Node head = array[index];
        while (head != null) {
            if(head.key == key) {
                return head.val;
            }
            head = head.next;
        }
        return -1;
    }

🥘测试:

ea3ca91b762246d8a1d70a9e6599b2d6.png

🍏方法二(引用类型)

那么我们知道Map的结构是<K,V>键值对模型,那么我们在实现的时候可以给出泛型<K,V>。

🍱1、内部类实现

    static class Node<K,V> {
        public K key;
        public V val;
        public Node<K,V> next;

        public Node(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node<K,V>[] array = (Node<K,V>[])new Node[10];
    public int usedSize;

🍊2、实现put方法

因为这里的key是引用类型不可以去%length,必须想办法转换为整型,那么这里就用到了我们的hashCode()方法,将引用类型转换为哈希码,再用hash%length。

40f8c1da0c714f7ebce519cf96afee4c.png🍋代码实现:

    public void put(K key,V val) {
        int hash = key.hashCode();
        int index = hash % array.length;
        Node<K,V> cur = array[index];
        while (cur != null) {
            if(cur.key.equals(key)) {
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        Node<K,V> node = new Node<>(key, val);
        node.next = array[index];
        array[index] = node;
        usedSize++;
    }

🥙3、测试类

那么注意,因为我们这里是使用的引用类型,那么必须重写hashCode()方法和equals()方法,hashCode()方法用来定位我们的数组下标,刚才有介绍过hashCode()方法是native int 类型的,equals()方法用来引用类型的比较,那么假如我们这里给一个成员变量id,那么这里我们来测试一下,重写hashCode()方法和equals()方法后:

   @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Person p1 = new Person("12345");
        Person p2 = new Person("12345");
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());
    }
}

🌽运行结果:

9c0685df0c8e47e4a428dcb6eca39176.png

我们发现结果是正确的!

🍻4、实现get方法

 public V get(K key) {
        int hash = key.hashCode();
        int index = hash % array.length;
        Node<K,V> cur = array[index];
        while (cur != null) {
            if(cur.key.equals(key)) {
                return cur.val;
            }
            cur = cur.next;
        }
        return null;
    }

面试问题:

🚦1、两个对象的hashCode一样,equals一定一样吗? 不一定

🚦2、两个对象的equals一样,hashCode一定一样吗?一定

 原因:

🍬🍬如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等,此时就出现所谓的哈希冲突场景。

🍼🍼如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。

总结:

🛥️🛥️1、hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

⛵⛵2、equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

目标检测 YOLOv5 - Rockchip rknn模型的测试 包括精度,召回率,mAP等详细信息

目标检测 YOLOv5 - Rockchip rknn模型的测试 包括精度&#xff0c;召回率&#xff0c;mAP等详细信息 flyfish 该测试是使用了自定义128张图片的测试结果&#xff0c;如果采用官网的coco128图片数据会比下列数值更好看。 以下是对比结果&#xff0c;pt模型的测试结果和rknn模型…

进程互斥的实现方法

文章目录进程互斥的软件实现方法单标志法双标志先检查法双标志后检查法Peterson算法img硬件实现进程互斥中断屏蔽方法TestAndSet指令Swap指令进程互斥的软件实现方法 软件实现方法的思想&#xff1a;在进入区设置并检查一些标志 来标明是否有进程在临界区中,若已有进程在临界区…

MyBatis

最近新开了个项目&#xff0c;记录第一次新开项目做得一些步骤&#xff0c;整合mybatis就是重要的一步&#xff0c;这里我演示了依赖的添加&#xff0c;逆向文件的生成。 1.整合mybatis 1.1基础配置 先添加依赖&#xff0c;再增加配置文件 dependencies <!--Spring Boot …

Java 开发工具 Eclipse

目录 一、Eclipse 概述 二、Eclipse 安装与汉化 三、创建 Java 项目 四、创建 Java 类 五、运行 Java 程序 六、Eclipse 调试程序 &#xff08;方法一&#xff09; 七、Eclipse 调试程序 &#xff08;方法二&#xff09; 工欲善其事&#xff0c;必先利…

Linux文件查找find

目录 前言 查找命令 命令演示 1.which&#xff1a;命令查找 2.locate命令 3.find命令&#xff08;主要使用这个命令进行查找文件&#xff09; 1&#xff09;语法 2&#xff09;find的用法介绍 按文件名查找 手动写入指定大小数据到文件内&#xff0c;介绍一下dd命令。…

【兄弟反目成仇系列】:我只是写了一个爆炸信息窗口脚本,好兄弟追了我几条街~

文章目录✨ 真的来咯~&#x1f4a5;爆炸信息窗口&#x1f4a1;设计思路&#x1f511;模块准备⚠️删除好友警告⚠️源代码❓这时你可能会问&#x1f440; 批量获取表情包&#x1f6c0;结束语专栏Python零基础入门篇&#x1f525;Python网络蜘蛛&#x1f525;Python数据分析Djan…

高德地图 API,点击地图标记获取自定义标记 (Marker) 中的信息

高德地图 API&#xff0c;点击地图标记获取自定义标记 (Marker) 中的信息 通过 高德地图 JS Web 添加自定义图标&#xff0c;自定义窗口标记 已经能在地图中正常添加自定义标记了 这篇文章讲讲点击标记时&#xff0c;如何从自定义标记中获取你需要的信息&#xff0c;如何预置一…

计算机网络【IP协议与以太网】

计算机网络【IP协议与核心协议】&#x1f34e;一.IP协议&#x1f352;1.1IPv4协议格式&#x1f352;1.2 IP协议地址&#x1f352;1.3IPv4协议的解决方案&#x1f352;1.4路由选择(了解)&#x1f34e;二.以太网协议&#x1f352;2.1以太网协议格式&#x1f352;2.2认识MTU(了解)…

聊一聊JAVA中的缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

为何需要规范 上一章中构建的最简化版本的缓存框架&#xff0c;虽然可以使用&#xff0c;但是也存在一个问题&#xff0c;就是它对外提供的实现接口都是框架根据自己的需要而自定义的。这样一来&#xff0c;项目集成了此缓存框架&#xff0c;后续如果想要更换缓存框架的时候&a…

哈希的应用

文章目录前言一、位图1.1位图概念1.2位图的实现1.3 位图的应用二、 布隆过滤器2.1 布隆过滤器提出2.2 布隆过滤器概念2.3 布隆过滤器的插入2.4 布隆过滤器的哈希函数2.5 布隆过滤器的查找2.6 布隆过滤器删除2.7 布隆过滤器的优点2.8 布隆过滤器的缺陷2.9 布隆过滤器的应用场景前…

散射辐射变送器的优势体现在哪些方面?

散射辐射是经过大气分子、水蒸气、灰尘等质点的散射&#xff0c;改变了方向的太阳辐射&#xff0c;也称天空散射辐射。太阳散射辐射强弱程度与太阳辐射的入射角、大气条件&#xff08;云量、水汽、砂粒、烟尘等粒子的多少&#xff09;和地面反射率有关。当天空的浑浊程度加大&a…

链路状态路由协议 OSPF (三)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.OSPF领接关系的建立 1.OSPF领接关系的建立概述 &#xff0…

彻底理解Java并发:乐观锁与CAS

本篇内容包括&#xff1a;悲观锁与乐观锁的概述、CAS&#xff08;Compare And Swap&#xff09;比较并交换的介绍、非阻塞算法与ABA问题&#xff0c;以及对 Java 中 CAS 的实现解读&#xff08;AtomicInteger 对 CAS 的实现&#xff0c;Unsafe 类简介&#xff09;。 一、悲观锁…

【树莓派不吃灰】Raspberry Pi上搭建NodeJS运行环境

目录1. 前言2. 安装NodeJS环境2.1 安装npm2.2 安装nodejs2.3 配置NPM国内镜像源3. 总结❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2022-10-28 ❤️❤️ 本篇更新记录 2022-10-28 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞…

嵌入式C语言编程中经验教训总结(八)变量、指针和指针数组的内存管理

目录嵌入式C语言编程中经验教训总结&#xff08;八&#xff09;变量、指针和指针数组的内存管理变量、指针和指针数组的内存占用指针、指针数组的空间验证指针数组的元素数据访问方法嵌入式C语言编程中经验教训总结&#xff08;八&#xff09;变量、指针和指针数组的内存管理 …

【趣学算法】第一章读书笔记

14天阅读挑战赛 *努力是为了不平庸~ 算法学习有些时候是枯燥的&#xff0c;这一次&#xff0c;让我们先人一步&#xff0c;趣学算法&#xff01; 文章目录1.1打开算法之门1.2 妙不可言——算法复杂性算法的特性好算法的标准时间复杂度和空间复杂度时间复杂度空间复杂度宕机1.4算…

62. 如何通过增强(Enhancement) 的方式给 SAP ABAP 标准程序增添新功能

文章目录 如何找到可以创建增强实现的增强点位置如何创建增强实现如何在 SE80 里找到增强实现本身如何调试 ABAP 增强实现总结ABAP 系统有比较完善的修改控制权限管控,比如笔者试图修改一个 SAP ABAP 系统里标准的函数,就会遇到如下的警告消息,然后修改的尝试会被阻止: You…

Winform和ASP.NET、Web API详解

Winform和ASP.NET、Web API 一、winform基础 1.1 基础学习 1、 winform应用程序是一种智能客户端技术&#xff0c;我们可以使用winform应用程序帮助我们获得信息或者传输信息等。 2、属性 Name:在后台要获得前台的控件对象&#xff0c;需要使用Name属性。 visible:指示一…

认识运营商机房

文章目录走线设备机房走线数据机房走线传输机房列头柜【供电】网络架构ONU设备OLT设备汇聚层交换机BARS设备核心路由器运营商网络架构【必看】铁塔基站核心机房ODF&#xff1a;光纤配线架MME光纤SGWPGWHSS交换机拓扑核心机房拓扑接入层基站&#xff08;BaseStation&#xff09;…

山西大同大学技术会,大同大学的家!

大家好&#xff0c;我是康来个程&#xff0c;山西大同大学技术会的创建者。 低谷时代 近几年校内的竞赛氛围越来越浓厚&#xff0c;随着自身参与并了解的赛事越来越多&#xff0c;随之而来的也是发现了我们学校竞赛方面的问题。疫情原因&#xff0c;我们的比赛取消的取消&…