手写一个简单的HashMap

简介: 手写一个简单的HashMap

什么是HashMap?

探讨HashMap之前我们要理清楚一个概念:哈希表

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

给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

然后是HashMap的概念

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

解决哈希冲突的常用方法

为什么会产生哈希冲突?

网络异常,图片无法展示
|


如何解决哈希冲突?

  • 开放定址法
    所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
  • 再哈希法
    再哈希法又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数
    计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。
  • 链地址法
    链地址法的基本思想是:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向
  • 建立公共溢出区
    这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

手写HashMap

/**
 * @desc: HashMap的简单实现
 * @author: YanMingXin
 * @create: 2021/8/9-9:46
 **/
public class MyHashMap<K, V> {
    /**
     * 默认容量16
     */
    private final int DEFAULT_CAPACITY = 16;
    /**
     * 内部存储结构
     */
    private Node[] table = new Node[DEFAULT_CAPACITY];
    /**
     * 长度
     */
    private int size = 0;
    /**
     * keySet
     */
    private Set<K> keySet;
    /**
     * 获取大小
     *
     * @return
     */
    public int size() {
        return this.size;
    }
    /**
     * 判断是否为空
     *
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }
    /**
     * get方法
     *
     * @param key
     * @return
     */
    public Object get(Object key) {
        int hashValue = hash(key);
        int i = indexFor(hashValue, table.length);
        for (Node node = table[i]; node != null; node = node.next) {
            if (node.key.equals(key) && hashValue == node.hash) {
                return node.value;
            }
        }
        return null;
    }
    /**
     * put方法
     *
     * @param key
     * @param value
     * @return
     */
    public Object put(Object key, Object value) {
        //通过key,求hash值
        int hashValue = hash(key);
        //通过hash,找到这个key应该放的位置
        int i = indexFor(hashValue, table.length);
        //i位置已经有数据了,往链表添加元素
        for (Node node = table[i]; node != null; node = node.next) {
            Object k;
            //且数组中有这个key,覆盖其value
            if (node.hash == hashValue && ((k = node.key) == key || key.equals(k))) {
                Object oldValue = node.value;
                node.value = value;
                //返回oldValue
                return oldValue;
            }
        }
        //如果i位置没有数据,或i位置有数据,但key是新的key,新增节点
        addEntry(key, value, hashValue, i);
        return null;
    }
    /**
     * 增加节点
     *
     * @param key
     * @param value
     * @param hashValue
     * @param i
     */
    public void addEntry(Object key, Object value, int hashValue, int i) {
        //如果超过了原数组大小,则扩大数组
        if (++size == table.length) {
            Node[] newTable = new Node[table.length * 2];
            System.arraycopy(table, 0, newTable, 0, table.length);
            table = newTable;
        }
        //的到i位置的数据
        Node eNode = table[i];
        //新增节点,将该节点的next指向前一个节点
        table[i] = new Node(hashValue, key, value, eNode);
    }
    /**
     * 获取插入的位置
     *
     * @param hashValue
     * @param length
     * @return
     */
    public int indexFor(int hashValue, int length) {
        return hashValue % length;
    }
    /**
     * 获取hash值
     *
     * @param key
     * @return
     */
    public int hash(Object key) {
        return key.hashCode();
    }
    /**
     * toString方法
     *
     * @return
     */
    @Override
    public String toString() {
        ArrayList list = new ArrayList<Node>();
        for (Node node : table) {
            if (node != null) {
                list.add(node);
            }
        }
        return list.toString();
    }
    /**
     * 静态内部类
     */
    static class Node<V> {
        int hash;//hash值
        Object key;//key
        Object value;//value
        Node<V> next;//指向下个节点(单例表)
        Node(int hash, Object key, Object value, Node<V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        public Object getKey() {
            return this.key;
        }
        public Object getValue() {
            return this.value;
        }
        @Override
        public String toString() {
            return "{" + key + ", " + value + "}";
        }
    }
}
复制代码


在本次HashMap的实现中就使用到了链地址法解决hash冲突,图示:

网络异常,图片无法展示
|


参考:

https://blog.csdn.net/m0_37499059/article/details/80623438

https://www.cnblogs.com/lyfstorm/p/11044468.html


相关文章
|
1月前
|
人工智能 机器人 测试技术
从支撑英伟达GR00T到登陆魔搭社区,智元AgiBot World打通具身智能全球数据生态
备受关注的 AgiBot World 百万真机数据集正式登陆国内顶级 AI开源社区——魔搭社区。该数据集由智元机器人开发,此前已在GitHub 和 Hugging Face 等国际平台开源并获得了业界的积极反响。此举是智元机器人布局国内AI生态的重要一步,国内开发者和研究者将能够更加顺畅地接入AgiBot World全套资源,降低数据获取和工具使用门槛,推动具身智能及机器人技术在国内的普及与发展。
98 0
|
4月前
【HarmonyOS Next之旅】ArkTS语法(四) -> 使用限制与扩展
本文介绍了ArkTS语言在生成器函数中的使用限制、变量的双向绑定以及自定义组件成员变量初始化的方式与约束。生成器函数中表达式的使用场景受限,且不能改变状态变量或包含局部变量。事件处理函数不受这些限制。双向绑定通过$$实现,支持基础类型及特定装饰器变量,变更时仅渲染当前组件以提升效率。成员变量初始化支持本地和构造参数两种方式,不同装饰器类型的变量有不同的初始化规则和约束,需根据具体需求选择合适的初始化方法。
185 21
|
SQL 安全 PHP
基于PHPCMS的SQL注入(Havij)
基于PHPCMS的SQL注入(Havij)
|
存储 安全 PHP
【文件上传绕过】——条件竞争漏洞
【文件上传绕过】——条件竞争漏洞
369 5
WK
|
11月前
|
开发者 Python
Python命名规范
Python命名规范为编写代码提供了一系列规则和约定,以增强代码的可读性、可维护性和一致性。其涵盖了项目、模块、包、类、异常、变量、函数及方法的命名方式,并强调了避免使用单字母命名、关键字和内置名称的重要性。遵循这些规范能够帮助开发者编写更清晰、统一且易懂的代码。
WK
766 2
|
人工智能 NoSQL Redis
如何将分布式锁性能提升100倍【含面试题】
如何将分布式锁性能提升100倍
619 0
|
存储 并行计算 NoSQL
如何利用Java进行大数据处理?
如何利用Java进行大数据处理?
|
存储 算法 安全
【C++ 泛型编程 C++14 新特性】理解C++14变量模板的魅力与应用
【C++ 泛型编程 C++14 新特性】理解C++14变量模板的魅力与应用
213 2
|
定位技术 Python
python实现迷宫小游戏(附源码 简单易懂)
python实现迷宫小游戏(附源码 简单易懂)
379 0
|
机器学习/深度学习 人工智能 算法
让模型训练速度提升2到4倍,「彩票假设」作者的这个全新PyTorch库火了
让模型训练速度提升2到4倍,「彩票假设」作者的这个全新PyTorch库火了
318 0
让模型训练速度提升2到4倍,「彩票假设」作者的这个全新PyTorch库火了