手写map

news2024/11/24 11:45:11

目录

  • 背景
  • 过程
    • 简介
    • 手写HashMap
    • 4、put方法
    • 5、get方法
    • 5、remove方法
  • 总结

背景

让我们来了解一下HashMap吧

过程

简介

HashMap是Java中一中非常常用的数据结构,也基本是面试中的“必考题”。它实现了基于“K-V”形式的键值对的高效存取。JDK1.7之前,HashMap是基于数组+链表实现的,1.8以后,HashMap的底层实现中加入了红黑树用于提升查找效率。

HashMap根据存入的键值对中的key计算对应的index,也就是它在数组中的存储位置。当发生哈希冲突时,即不同的key计算出了相同的index,HashMap就会在对应位置生成链表。当链表的长度超过8时,链表就会转化为红黑树。
在这里插入图片描述

手写HashMap

1、定义接口

public interface MyMap<K,V> {

    V put(K k, V v);

    V get(K k);

    int size();

    V remove(K k);

    boolean isEmpty();

    void clear();
}

2、实现接口,实现这个接口,并实现里面的方法。

   final static int DEFAULT_CAPACITY = 16;
    final static float DEFAULT_LOAD_FACTOR = 0.75f;

    int capacity;
    float loadFactor;
    int size = 0;

    Entry<K,V>[] table;
Copy
class Entry<K, V>{
    K k;
    V v;
    Entry<K,V> next;

    public Entry(K k, V v, Entry<K, V> next){
        this.k = k;
        this.v = v;
        this.next = next;
    }
}

我们参照HashMap设置一个默认的容量capacity和默认的加载因子loadFactor,table就是底层数组,Entry类保存了"K-V"数据,next字段表明它可能会是一个链表节点。

3、构造方法

public MyHashMap(){
    this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public MyHashMap(int capacity, float loadFactor){
    this.capacity = upperMinPowerOf2(capacity);
    this.loadFactor = loadFactor;
    this.table = new Entry[capacity];
}

这里的upperMinPowerOf2的作用是获取大于capacity的最小的2次幂。在HashMap中,开发者采用了更精妙的位运算的方式完成了这个功能,效率比这种方式要更高。

private static int upperMinPowerOf2(int n){
    int power = 1;
    while(power <= n){
        power *= 2;
    }
    return power;
}

为什么HashMap的capacity一定要是2次幂呢?这是为了方便HashMap中的数组扩容时已存在元素的重新哈希(rehash)考虑的。

4、put方法

@Override
public V put(K k, V v) {
    // 通过hashcode散列
    int index = k.hashCode() % table.length;
    Entry<K, V> current = table[index];
    // 判断table[index]是否已存在元素
    // 是
    if(current != null){
        // 遍历链表是否有相等key, 有则替换且返回旧值
        while(current != null){
            if(current.k == k){
                V oldValue = current.v;
                current.v = v;
                return oldValue;
            }
            current = current.next;
        }
        // 没有则使用头插法
        table[index] = new Entry<K, V>(k, v, table[index]);
        size++;
        return null;
    }
    // table[index]为空 直接赋值
    table[index] = new Entry<K, V>(k, v, null);
    size++;
    return null;
}

put方法中,我们通过传入的K-V值构建一个Entry对象,然后判断它应该被放在数组的那个位置。回想我们之前的论断:

想要提高HashMap的效率,最重要的就是尽量避免生成链表,或者说尽量减少链表的长度

想要达到这一点,我们需要Entry对象尽可能均匀地散布在数组table中,且index不能超过table的长度,很明显,取模运算很符合我们的需求int index = k.hashCode() % table.length。关于这一点,HashMap中也使用了一种效率更高的方法——通过&运算完成key的散列,有兴趣的同学可以查看HashMap的源码。

如果table[index]处已存在元素,说明将要形成链表。我们首先遍历这个链表(长度为1也视作链表),如果存在key与我们存入的key相等,则替换并返回旧值;如果不存在,则将新节点插入链表。插入链表又有两种做法:头插法和尾插法。如果使用尾插法,我们需要遍历这个链表,将新节点插入末尾;如果使用头插法,我们只需要将table[index]的引用指向新节点,然后将新节点的next引用指向原来table[index]位置的节点即可,这也是HashMap中的做法。
在这里插入图片描述

5、get方法

@Override
public V get(K k) {
    int index = k.hashCode() % table.length;
    Entry<K, V> current = table[index];
    // 遍历链表
    while(current != null){
        if(current.k == k){
            return current.v;
        }
        current = current.next;
    }
    return null;
}

5、remove方法

@Override
public V remove(K k) {
    int index = k.hashCode() % table.length;
    Entry<K, V> current = table[index];
    // 如果直接匹配第一个节点
    if(current.k == k){
        table[index] = null;
        size--;
        return current.v;
    }
    // 在链表中删除节点
    while(current.next != null){
        if(current.next.k == k){
            V oldValue = current.next.v;
            current.next = current.next.next;
            size--;
            return oldValue;
        }
        current = current.next;
    }
    return null;
}

移除某个节点时,如果该key对应的index处没有形成链表,那么直接置为null。如果存在链表,我们需要将目标节点的前驱节点的next引用指向目标节点的后继节点。由于我们的Entry节点没有previous引用,因此我们要基于目标节点的前驱节点进行操作,即:

Copy
current.next = current.next.next;
current代表我们要删除的节点的前驱节点。

还有一些简单的size()、isEmpty()等方法都很简单,这里就不再赘述。现在,我们自定义的MyHashMap基本可以使用了。

总结

关于HashMap的实现,还有几点我们没有解决:

扩容问题。在HashMap中,当存储的元素数量超过阈值(threshold = capacity * loadFactor)时,HashMap就会发生扩容(resize),然后将内部的所有元素进行rehash,使hash冲突尽可能减少。在我们的MyHashMap中,虽然定义了加载因子,但是并没有使用它,capacity是固定的,虽然由于链表的存在,仍然可以一直存入数据,但是数据量增大时,查询效率将急剧下降。
树化问题(treeify)。我们之前讲过,链表节点数量超过8时,为了更高的查询效率,链表将转化为红黑树。但是我们的代码中并没有实现这个功能。
null值的判断。HashMap中是允许存null值的key的,key为null时,HashMap中的hash()方法会固定返回0,即key为null的值固定存在table[0]处。这个实现起来很简单,不实现的情况下MyHashMap中如果存入null值会直接报NullPointerException异常。
一些其他问题。

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

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

相关文章

Docker容器的tomcat安装后访问报404页面的解决办法

上次我们创建的tomcat容器访问的时候是404页面,是因为高版本的并没有把默认的页面放到webapps目录下,这时,就需要我们登录创建的tomcat容器了 登录tomcat容器: docker exec -it my_tomcat /bin/bash 查看当前目录: ls 将webapp.dist下的默认页面复制到webapps目录下: cp …

unity3d:YooAsset零冗余构建Assetbundle代码分析

BuildAssetInfo构建asset信息 1.每个收集器下asset会构建出BuildAssetInfo&#xff0c;这种asset是没有冗余&#xff0c;只有依赖列表 2.每个依赖asset会构建出BuildAssetInfo&#xff0c;会记录将要打入的bundle列表 依赖的Asset列表 这个asset依赖的其他asset列表&#xf…

Tree 树结构

Case 1st 最少的摄像头——亚马逊面试问题 给定一个二叉树&#xff0c;我们在树的节点上安装摄像头。 节点上的每个摄像机都可以监视其父级、自身及其直接子级。 计算监视树的所有节点所需的最小摄像机数。 例&#xff1a; Input: [0,0,null,0,0]Output: 1Explanation: One cam…

asp.net宠物购物商城系统MyPetShop

asp.net宠物购物商城系统 在线购物网站&#xff0c;电子商务系统 主要技术&#xff1a; 基于asp.net架构和sql server数据库 功能模块&#xff1a; 用户可以购买宠物&#xff0c;查看订单记录 修改密码等 运行环境&#xff1a; 运行需vs2013或者以上版本&#xff0c;sql serv…

183 · 木材加工

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a;九章算法 - 帮助更多程序员找到好工作&#xff0c;硅谷顶尖IT企业工程师实时在线授课为你传授面试技巧 class Solution { public:/*** param l: Given n pieces of wood wi…

Java8 Stream详解

Stream类继承关系 前置知识 Spliterator接口使用 Spliterator是在java 8引入的一个接口&#xff0c;它通常和stream一起使用&#xff0c;用来遍历和分割序列。 只要用到stream的地方都需要Spliterator&#xff0c;比如List&#xff0c;Collection&#xff0c;IO channel等等…

大语言模型的百家齐放

基础语言模型 概念 基础语言模型是指只在大规模文本语料中进行了预训练的模型&#xff0c;未经过指令和下游任务微调、以及人类反馈等任何对齐优化。 如何理解 只包含纯粹的语言表示能力,没有指导性或特定目标。 只在大量无标注文本上进行无监督预训练,用于学习语言表示。 …

unity制作手游fps僵尸游戏

文章目录 介绍制作基本UI枚举控制角色移动切枪、设置音效、设置子弹威力、设置子弹时间间隔、换弹准星控制射击僵尸动画、血条设置导航 介绍 利用协程、枚举、动画器、导航等知识点。 实现移动、切枪、换弹、射击、僵尸追踪、攻击。 制作基本UI 制作人类血条、僵尸血条、移动按…

百度智能车竞赛丝绸之路1——智能车设计与编程实现控制

百度智能车竞赛丝绸之路1——智能车设计与编程实现控制 百度智能车竞赛丝绸之路2——手柄控制 一、项目简介 本项目现已基于鲸鱼机器人开发套件对其整体外形进行设计&#xff0c;并且对应于实习内容——以“丝绸之路”为题&#xff0c;对机器人各个功能与机器人结构部分进行相…

【几何数学】【Python】【C++】判断两条线段是否相交,若相交则求出交点坐标

判断线段是否相交的办法&#xff08;使用了向量叉积的方法&#xff09;&#xff1a; 首先&#xff0c;通过给定的线段端点坐标p1、p2、p3和p4构建了四个向量v1、v2、v3和v4&#xff1a; v1表示从p1指向p2的向量&#xff0c;其分量为[p2[0] - p1[0], p2[1] - p1[1]]。 v2表示从…

Camtasia Studio2023标准版屏幕录制和视频剪辑软件

Camtasia Studio2023提供了强大的屏幕录像、视频的剪辑和编辑、视频菜单制作、视频剧场和视频播放功能等。它能在任何颜色模式下轻松地记录屏幕动作&#xff0c;包括影像、音效、鼠标移动的轨迹&#xff0c;解说声音等等&#xff0c;另外&#xff0c;它还具有及时播放和编辑压缩…

[前端]JS——join()与split()的使用

Array.join():数组转换为字符串,"()"里元素指定数组转为字符串用什么串联&#xff0c;默认为空。 Array.join()的使用&#xff1a; <script>let arr[1,2,3,4]console.log("arr未转换前:",arr,typeof(arr));console.log("arr使用join():"…

Netty核心技术八--Netty编解码器和handler的调用机制

1.基本说明 netty的组件设计&#xff1a;Netty的主要组件有Channel、EventLoop、ChannelFuture、 ChannelHandler、ChannelPipe等 ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。 例如&#xff0c;实现ChannelInboundHandler接口&#xff08;或ChannelInbound…

Typora图床配置-OSS对象存储

Typora图床配置-OSS对象存储 1.PicGo下载 下载地址&#xff1a; Release 2.3.0 Molunerfinn/PicGo GitHub https://github.com/Molunerfinn/PicGo/releases/tag/v2.3.1 下载如下&#xff1a; 2.安装和配置 进入阿里云创建OSS对象存储服务。 设置为公共读才能被别人访问到。…

树与图的深度优先遍历

树的重心 本题的本质是树的dfs&#xff0c; 每次dfs可以确定以u为重心的最大连通块的节点数&#xff0c;并且更新一下ans。 也就是说&#xff0c;dfs并不直接返回答案&#xff0c;而是在每次更新中迭代一次答案。 这样的套路会经常用到&#xff0c;在 树的dfs 题目中 #includ…

IMU 互补滤波

IMU学名惯性测量单元&#xff0c;所有的运动都可以分解为一个直线运动和一个旋转运动&#xff0c;故这个惯性测量单元就是测量这两种运动&#xff0c;直线运动通过加速度计可以测量&#xff0c;旋转运动则通过陀螺。 void IMUupdate(float gx, float gy, float gz, float ax,fl…

Go 语言 context 都能做什么?

原文链接&#xff1a; Go 语言 context 都能做什么&#xff1f; 很多 Go 项目的源码&#xff0c;在读的过程中会发现一个很常见的参数 ctx&#xff0c;而且基本都是作为函数的第一个参数。 为什么要这么写呢&#xff1f;这个参数到底有什么用呢&#xff1f;带着这样的疑问&am…

postgresql数据库登录代理解析(包含登录协议包解析)

文章目录 postgresql数据库登录代理解析&#xff08;包含登录协议包解析&#xff09;背景描述版本不同对应的账号密码加密目标解析方法相关代码位置断点关键位置及相关重要变量 登录通信流程&#xff08;SCRAM-SHA-256方式&#xff09;代码实现相关参考资料 postgresql数据库登…

Python count()函数详解

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 count 1、指定搜索位置2、参数为负数3、列表的coun…

以太网OSI参考模型(四)

目录 OSI模型 一、物理层 二、数据链路层 三、网络层 四、传输层 五、会话层 六、表示层 七、应用层 OSI模型 OSI七层模型&#xff0c;是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)1984年联合制定的开放系统互联参考模型&#xff0c;为开放式互联信息系统提供…