返回顶部
首页 > 资讯 > 后端开发 > Python >Java模拟实现HashMap算法流程详解
  • 382
分享到

Java模拟实现HashMap算法流程详解

JavaHashMapJava模拟实现HashMap 2023-02-08 18:02:28 382人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录1、前言2、成员变量的设定3、构造方法4、hash方法以及阈值判断方法5、put方法6、resize 方法7、get 方法1、前言 上期讲解了 HashMap 和 HashSet

1、前言

上期讲解了 HashMap 和 HashSet 的一些相关源码,本期我们就来简单的模拟实现一下 HashMap,当然肯定没有源码那么的复杂,但是简单的结构还是要去实现一下的,当然,这也是数据结构课程中最后一起了,后续博主也会带来 Mysql基础,和 Java EE 一些相关的内容。如果数据结构在学习的过程中,感到特别困难的话,记得多画图,多调试。

2、成员变量的设定

public class MyHashMap<K, V> {
    private Entry<K, V>[] table; //哈希表
    private int size; // 有效数据个数
    private static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子设定
    private static final int DEFAULT_CAPACITY = 6; //默认容量
    // 节点
    public static class Entry<K, V> {
        private K key;
        private V value;
        private Entry<K, V> next;
        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
}

这里我们采用数组来存储我们的数据,而每个数组的元素是 Entry 这样的节点,Entry 中包含一个 next 引用,用来存放下一个节点,从而实现数组中每个元素可以是一个链表的结构,那么大概是这样的一个结构:

通常我们画数组都是横过来画的,只不过这次我们把数组竖过来画的,这样能更清晰看到链表的结构,从美观上也更漂亮。

简而言之,我们今天实现的结构就是数组元素中放链表的结构,当然涉及到了泛型的知识,如果对泛型还不够了解,可以看下博主 JavaSE 系列泛型的博客。

3、构造方法

public MyHashMap() {
    this.table = (Entry<K, V>[])new Entry[DEFAULT_CAPACITY];
    this.size = 0;
}
public MyHashMap(int capacity) {
    this.table = (Entry<K, V>[])new Entry[capacity];
    this.size = capacity;
}

因为是模拟实现,我们就尽可能的简化,体现出 HashMap 底层的结构即可,这我们就默认令 HashMap 的大小为6,也有一个带参数的构造方法,可以指定哈希表的大小。

有了上述的准备工作,我们这里就可以来实现下主要的几个方法了,主要实现 put,get,resize(扩容),hash 这些方法。

4、hash方法以及阈值判断方法

这里我们简单设计一下即可,就获取对象的 hashCode % 哈希表的长度即可:

private int hash(K key) {
    return (key == null) ? 0 : key.hashCode() % table.length;
}

判断是否达到阈值,也就是是否超过设定的负载因子了,就需要考虑扩容的情况,上期介绍到,求负载因子:哈希表的长度 / 元素个数,有了这样的公式,那自然就好判断是否到达了阈值了:

private boolean loadFactor() {
    return size * 1.0 / table.length >= DEFAULT_LOAD_FACTOR;
}

5、put方法

jdk1.7 及之前链表采用的是头插的方式,JDK1.8 及以后采用的是尾插方式,那么我们就来模拟实现一下尾插的一个逻辑。

这里思考插入时的两种情况:

1. 通过 hash 值,得到哈希表的位置上不存在元素,也就是 hash 位置为 null 的情况下。

直接在当前哈希表元素 new 一个节点插入即可。

我们就按照上述的两种情况来进行插入元素。

2. 通过 hash 值,得到哈希表的位置上已经存在元素了,也就是 hash 位置 不为 null 的情况下。

  • 遍历链表没有与要插入的 key 相同的元素的情况下,直接在最后插入。
  • 遍历链表发现了存在相同 key 的元素的情况下,更新 key 对应的 value 值,即可。

分析上述的情况,也有图解的情况,接下来就可以来实现我们的代码了:

public V put(K key, V value) {
    return putVal(hash(key), key, value);
}
private V putVal(int hash, K key, V value) {
    // 通过 hash 值, 找到对应位置
    Entry<K, V> cur = table[hash];
    Entry<K, V> prev = null;
    if (cur == null) {
        table[hash] = new Entry<>(key, value);
    }  else {
        while (cur != null) {
            // 碰到相同值的情况
            if (cur.key.equals(key)) {
                cur.value = value; //更新下value值
                return value;
            }
            prev = cur;
            cur = cur.next;
        }
        // prev 后面插入节点
        prev.next = new Entry<>(key, value);
    }
    size++;
    // 判断是否超过了阈值考虑扩容问题。
    if (loadFactor()) {
        resize();
    }
    return value;
}

这里我是采用 prev 记录 cur 的前一个节点,当 cur 为 null 就结束循环了,进行尾插,当然你也可也当 cur.next 为 null,结束循环,最后使得 cur.next = new Entry<>(key,value) 也是可以的。

这里每插入一个元素,都需要判断是否超过了设定的 0.75 的负载因子了,如果超过的话,就需要重新调整哈希表的大小。

6、resize 方法

给哈希表扩容的目的就是减少冲突的概率,但是这里得考虑到一个问题,可以直接扩容吗?我们 hash 函数设置是 key.hashCode % table.length,那么如果哈希表的长度改变了,之前表中元素 key 对应的 hash 值也会发生改变,所以我们通过新的 hash 值,不一定能找到之前元素的位置了。所以扩容之后,原来表中所有元素的位置都要通过新的 hash 值放入新的位置上。

private void resize() {
    // 重新扩容势必会考虑到一个问题, 重新 hash 的问题
    // 即现在表中的元素, 要通过新的 hash 值, 放入扩容后新的位置上
    // 二倍式扩容
    Entry<K, V>[] oldTable = table;
    table = (Entry<K, V>[])new Entry[table.length * 2];
    // 将 oldTable 的数据通过新的 hash, 拷贝进 table 中
    copyData(oldTable);
}
private void copyData(Entry<K, V>[] oldTable) {
    // 遍历这个 oldTable 将数据拷贝进他 table 中
    for (int i = 0; i < oldTable.length; i++) {
        Entry<K, V> node = oldTable[i];
        while (node != null) {
            // 不能直接将 node 插入进去, 因为 node.next 后面可能还有其他元素
            // 所以我们要拷贝一份新的 node 进行插入
            Entry<K, V> insertNode = new Entry<>(node.key, node.value);
 
            int index = hash(node.key); // node要被hash到的新位置
            Entry<K, V> cur = table[index];
            // table当前位置没有元素, 直接插入该节点作为链表的头节点
            if (cur == null) {
                table[index] = insertNode;
            } else {
                Entry<K, V> prev = null;
                // 如果当前数组下标已经有元素了, 就遍历数组中链表往后找
                while (cur != null) {
                    prev = cur;
                    cur = cur.next;
                }
                prev.next = insertNode;
            }
            // 插入 oldTable[i] 当前链表中节点后, node 往后走, 判断还有没有节点需要重新 hash 插入
            node = node.next;
        }
    }
}

表中的每个元素是一个链表,但是需要对个元素进行重新 hash,不能直接移动整条链表,只能拿出每个元素,分别重新 hash 放入新的位置上,所以这里我采取将老节点复制出来进行重新hash。

这里我也写了一个测试样例,来证明该代码上下文代码结合跑起来扩容后是会重新 hash 放入新的位置的:

public static void main(String[] args) {
    MyHashMap<Integer, Integer> map = new MyHashMap<>();
    map.put(1, 1);
    map.put(17, 2);
    map.put(21, 3);
    map.put(11, 4);
    System.out.println("扩容前: ");
    System.out.println(map);
    map.put(13, 5);
    System.out.println("扩容后: ");
    System.out.println(map);
}

由于默认数组大小是 6,当插入完第五个元素后,则会达到阈值,就需要扩容了,这里我重写了 toString 方法,能看到打印结果就是模拟出数组加链表结构的打印,很明显能看到,扩容前,5 下标位置有 key=17 和 key=11 两个元素,而扩容后 5 下标只剩下 key = 17 一个元素了,而 key=11 则被重新 hash 到了 11 位置下。

7、get 方法

这个方法就比较简单了,通过传过来的 key 返回对应 key 的 value值,利用传过来 key 通过 hash 函数获取 index 位置,这个位置可能没有元素,可能是一条链表,但链表中也可能不存在key,也可能存在 key,如果 index 位置没有元素,或者遍历 index 位置都没找到 key,那么就返回 null,找到了即返回 key 对应的 value 值即可。代码如下:

public V get(K key) {
    // 通过 hash 获取当前 key 所在的位置
    int index = hash(key);
    // 通过 index 位置找到 key 对应的 value
    Entry<K, V> cur = table[index];
    while (cur != null) {
        if (cur.key.equals(key)) {
            return cur.value;
        }
        cur = cur.next;
    }
    return null;
}

这次模拟实现 HashMap 相较于源码我们还是简单了很多,主要是练习数组加链表这样的一个结构,和练习重新hash 的一个问题。那么 Java 实现数据结构初阶的内容到此就告一段落了

到此这篇关于Java模拟实现HashMap算法流程详解的文章就介绍到这了,更多相关Java HashMap内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java模拟实现HashMap算法流程详解

本文链接: https://lsjlt.com/news/195240.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java模拟实现HashMap算法流程详解
    目录1、前言2、成员变量的设定3、构造方法4、hash方法以及阈值判断方法5、put方法6、resize 方法7、get 方法1、前言 上期讲解了 HashMap 和 HashSet...
    99+
    2023-02-08
    Java HashMap Java模拟实现HashMap
  • C++模拟实现vector流程详解
    目录模拟vector总结模拟vector 我们可以通过模板实现类似vector的类。我们实现一个StrVecTemp类,其内部通过allocator开辟空间,存储的类型用T来表示,T...
    99+
    2022-11-13
    C++ vector容器 C++ vector
  • C++String部分成员模拟实现流程详解
    目录string类的成员设计普通构造函数的模拟拷贝构造函数的模拟赋值重载函数的模拟String的析构函数模拟补全上述的成员函数迭代器的简单模拟其他成员函数的模拟string类的成员设...
    99+
    2024-04-02
  • Java实现HashMap排序方法的示例详解
    目录简介排序已有数据按key排序按value排序按插入顺序存放HashMap不按插入顺序存放LinkedHashMap会按照插入顺序存放简介 本文用示例介绍HashMap排序的方法。...
    99+
    2024-04-02
  • Java详解实现ATM机模拟系统
    目录一、概述二、程序概要设计三、程序详细设计四、程序演示一、概述 (1)选题分析 (2) 开发环境 开发环境,选择IDEA这一Java开发软件,基于JDK1.8版本,在本机windo...
    99+
    2024-04-02
  • Java语言实现快速幂取模算法详解
    快速幂取模算法的引入是从大数的小数取模的朴素算法的局限性所提出的,在朴素的方法中我们计算一个数比如5^1003%31是非常消耗我们的计算资源的,在整个计算过程中最麻烦的就是我们的5^1003这个过程缺点1:在我们在之后计算指数的过程中,计算...
    99+
    2023-05-30
    java 快速幂取模 算法
  • C++ qsort函数排序与冒泡模拟实现流程详解
    目录一、qsort排序函数1、函数功能特点2、函数参数3、比较函数4、测试qsort排序(1)测试qsort函数排序整形(2)测试qsort函数排序结构体二、冒泡模拟qusort函数...
    99+
    2022-11-13
    C++ qsort函数排序 C++冒泡排序
  • Java模拟实现机场过安检处理流程
    目录一 场景描述二 实战三 测试四 问题分析五 说明一 场景描述 乘飞机时,在进入登机口之前必须经过安全检查,安检口类似于...
    99+
    2024-04-02
  • 详解Java实现分治算法
    目录一、前言二、分治算法介绍三、分治算法经典问题3.1、二分搜索3.2、快速排序3.3、归并排序(逆序数)3.4、最大子序列和3.5、最近点对四、结语一、前言 在学习分治算法之前,问...
    99+
    2024-04-02
  • Vueecharts模拟后端数据流程详解
    目录KOA2的使用安装 Koaapp.js入口文件KOA2的使用 KOA2是由Express 原班人马打造、 环境依赖 Node v7.6.0 及以上、 支持 async 和 awa...
    99+
    2024-04-02
  • java 中模式匹配算法-KMP算法实例详解
    java 中模式匹配算法-KMP算法实例详解朴素模式匹配算法的最大问题就是太低效了。于是三位前辈发表了一种KMP算法,其中三个字母分别是这三个人名的首字母大写。简单的说,KMP算法的对于主串的当前位置不回溯。也就是说,如果主串某次比较时,当...
    99+
    2023-05-31
    java kmp ava
  • C++模拟实现string的方法详解
    目录1.string 成员变量2.构造函数3.拷贝构造、赋值重载和析构函数1.拷贝构造2.赋值重载3.析构函数4.访问成员变量5.遍历1.下标+【】2.迭代器(iterator)3....
    99+
    2022-11-13
    C++实现string C++ string
  • 详解Java实现简单SPI流程
    目录SPI标注注解SPI核心实现SPI的一些Class和扩展对象缓存获取扩展器ExtensionLoader扩展加载器构造方法获取SPI扩展对象创建扩展对象从Holder中获取获取扩...
    99+
    2023-03-02
    Java实现SPI Java SPI Java实现简单SPI
  • javascript实现计算器功能详解流程
    目录1、计算器功能介绍2、计算器页面设计1、导航栏部分2、数据部分3、index.wxml布局页面4、index.css样式页面5、运行结果3、功能实现部分1、删除功能2、清空功能3...
    99+
    2024-04-02
  • 模拟简单Java线程池的方法详解
    目录一、 前言二、线程池是什么?三、线程池构造方法ThreadPoolExecutor的构造方法的参数都是啥意思?四、模拟实现一个线程池总结一、 前言 为了实现并发编程,于是就引入了...
    99+
    2024-04-02
  • Python多进程协作模拟实现流程
    由于python多线程无法发挥多核的作用,因此当计算量很大的时候就需要考虑多进程。 只不过多进程比较麻烦一些,进程中通信向来是一件麻烦事。 python提供了multiprocess...
    99+
    2023-01-17
    Python多进程协作 Python多进程协作模拟
  • Java实现快速幂算法详解
    目录前言1. 暴力算法(fail)2. 优化取模运算(accept)3. 优化时间复杂度(accept)4. 优化 位运算(accept)前言 此算法偶尔会出现在笔试以及面试中,特意...
    99+
    2022-11-13
    Java实现快速幂算法 Java快速幂算法 Java快速幂
  • Java详解HashMap实现原理和源码分析
    目录学习要点:1、什么是HashMap?2、HashMap的特性3、HashMap的数据结构4、HashMap初始化操作4.1、成员变量4.2、 构造方法5、Jdk8中HashMap...
    99+
    2024-04-02
  • 详解C++ STL模拟实现list
    目录list 概述接口总览list 的节点默认成员函数默认构造函数析构函数拷贝构造函数复制赋值函数list 的迭代器构造函数operator==operator!=operator*...
    99+
    2023-01-11
    C++ STL实现list C++ STL list
  • 详解C++STL模拟实现forward_list
    目录forward_list 概述接口总览forward_list 的节点默认成员函数默认构造函数析构函数forward_list 的迭代器构造函数operator==operato...
    99+
    2023-01-13
    C++ STL实现forward_list C++ STL forward_list
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作