【轻松拿捏】HashMap-详解及底层实现原理?

news2024/9/22 5:38:57

目录

1. 基本结构

2. 哈希函数

3. 哈希冲突解决

4. 插入操作(put)

5. 查找操作(get)

6. 删除操作(remove)

7. 扩容(resize)

8.说一下 HashMap 的实现原理?(面试)

8.1. 哈希函数

8.2. 数组

8.3. 冲突处理

8.4. 负载因子和扩容

8.5. 操作时间复杂度

8.6. Java 中的 HashMap 细节

9.总结


🎈边走、边悟🎈迟早会好

        HashMap  是一种非常常用的数据结构,特别适合需要高效查找、插入和删除操作的场景。下面将对 HashMap的内部机制进行详细讲解。

1. 基本结构

        HashMap是基于哈希表实现的。在 Java 中,它由一个数组和链表(在 Java 8 之前)或红黑树(在 Java 8 及之后)组成。

  • 数组:存储键值对的基础结构。
  • 链表:用于解决哈希冲突的链地址法的一种实现。
  • 红黑树:当链表长度超过一定阈值时,链表会转换为红黑树,以优化性能。

2. 哈希函数

哈希函数用于将键映射到数组中的一个索引位置。Java 中,HashMap的哈希函数通过以下步骤生成:

  1. 计算键的哈希值:使用 hashCode() 方法计算键的哈希值。
  2. 扰动函数:通过高位参与运算,减少冲突。Java 使用的扰动函数如下:
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

3. 哈希冲突解决

  • 链地址法(Chaining):在一个桶中存储一个链表,当多个键映射到同一个桶时,这些键值对存储在链表中。
  • 红黑树:当一个桶中的链表长度超过 8 时,链表转换为红黑树,提高查找、插入和删除操作的效率。

4. 插入操作(put)

        插入一个键值对时,首先计算键的哈希值,然后确定数组索引。如果该索引处没有元素,则直接插入。如果该索引处有元素(哈希冲突),则检查链表或红黑树中是否已经存在该键。如果存在则更新值,否则将新键值对插入到链表或红黑树中。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

5. 查找操作(get)

        查找键对应的值时,计算键的哈希值,然后定位到数组的索引位置。如果该位置为空,则返回 null。如果该位置不为空,则遍历链表或红黑树,查找对应的键值对。

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

6. 删除操作(remove)

        删除键值对时,计算键的哈希值,然后定位到数组的索引位置。如果该位置为空,则返回 null。如果该位置不为空,则遍历链表或红黑树,找到并移除对应的键值对。

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
}

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

7. 扩容(resize)

        当 HashMap 中的元素个数超过一定比例(负载因子)时,HashMap 会进行扩容,将数组容量加倍,并重新哈希所有键值对到新的数组中。这是一个代价较高的操作,但保证了 HashMap 的高效性。

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap

8.说一下 HashMap 的实现原理?(面试)

        HashMap是一种常用的数据结构,特别是在需要高效查找、插入和删除操作时。它的实现主要依赖于哈希表(Hash Table)。以下是 HashMap的实现原理:

8.1. 哈希函数

        HashMap使用哈希函数将键(key)映射到一个桶(bucket)或槽位(slot)。哈希函数接受键并返回一个整数值,这个值用来定位存储数据的数组索引。

8.2. 数组

        哈希表的核心是一个数组。每个数组元素称为一个桶,存储键值对(key-value pair)。在 Java 中,初始数组的默认大小是 16。

8.3. 冲突处理

        由于不同的键可能映射到相同的数组索引,称为哈希冲突,HashMap需要一种机制来处理冲突。常见的冲突处理方法包括:

  • 链地址法(Chaining):每个桶中存储一个链表,当多个键映射到同一个桶时,这些键值对被存储在链表中。
  • 开放地址法(Open Addressing):如果发生冲突,寻找数组中的下一个空闲位置存储键值对。常见的策略包括线性探测(Linear Probing)、二次探测(Quadratic Probing)和双重哈希(Double Hashing)。
8.4. 负载因子和扩容

        负载因子(Load Factor)是哈希表已使用容量与总容量的比例。在 Java 的 HashMap实现中,默认负载因子是 0.75。当实际负载因子超过阈值时,HashMap会进行扩容,将数组大小翻倍,并重新哈希所有键值对到新的数组中。

8.5. 操作时间复杂度
  • 插入(Put):在理想情况下,时间复杂度为 O(1)。当发生冲突并且链表很长时,最坏情况时间复杂度为 O(n)。
  • 查找(Get):在理想情况下,时间复杂度为 O(1)。同样地,冲突处理不当时最坏情况时间复杂度为 O(n)。
  • 删除(Remove):在理想情况下,时间复杂度为 O(1)。冲突处理不当时最坏情况时间复杂度为 O(n)。
8.6. Java 中的 HashMap 细节

        在 Java 8 及之后的版本中,为了优化性能,当链表长度超过一定阈值(默认为 8)时,链表会转换为红黑树(Red-Black Tree),从而将查找、插入和删除的最坏情况时间复杂度从 O(n) 改善到 O(log n)。

9.总结

        HashMap 通过哈希函数将键映射到数组索引,并使用链地址法或开放地址法处理冲突。为了保持高效操作,HashMap使用负载因子和扩容机制,并在 Java 8 后引入了红黑树以优化长链表的性能。通过这些机制,HashMap提供了高效的插入、查找和删除操作。

 

 🌟感谢支持 听忆.-CSDN博客

🎈众口难调🎈从心就好

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

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

相关文章

前端:Vue学习-1

前端:Vue学习-1 1. 指令1. 指令修饰符2. v-bind对样式控制的增强3. v-model应用于其他表单元素 2. 计算属性3. watch侦听器&#xff08;监视器&#xff09; 1. 指令 就是带有v-前缀的特殊属性&#xff0c;不同属性对应不同的功能 v-html&#xff1a;动态设置页面的html标签内容…

平替ChatGPT的多模态智能体来了

在人工智能领域&#xff0c;多模态技术的融合与应用已成为推动技术革新的关键。今天&#xff0c;我们用智匠AI实现了完全由国产模型驱动的多模态智能体——智酱v0.1.0&#xff0c;它不仅能够媲美ChatGPT的多模态能力&#xff0c;更在联网搜索、图片识别、画图及图表生成等方面展…

罗技K380无线键盘及鼠标:智慧互联,一触即通

目录 1. 背景2. K380无线键盘连接电脑2.1 键盘准备工作2.2 电脑配置键盘的连接 3. 无线鼠标的连接3.1 鼠标准备工作3.2 电脑配置鼠标的连接 1. 背景 有一阵子经常使用 ipad&#xff0c;但是对于我这个习惯于键盘打字的人来说&#xff0c;慢慢在 ipad 上打字&#xff0c;实在是…

WEB-INF 泄露-RoarCTF-2019-EasyJava(BUUCTF)

题目页面 点开help 这里存在文件下载漏洞&#xff0c;参数选择POST传参&#xff08;使用HackBar插件&#xff09; 查看文件内容 下载存有web信息的XML文件&#xff0c;这里补充一点知识点 WEB-INF主要包含一下文件或目录&#xff1a; /WEB-INF/web.xml&#xff1a;Web应用程序…

Qt会议室项目

在Qt中编写会议室应用程序通常涉及到用户界面设计、网络通信、音频/视频处理等方面。以下是创建一个基本会议室应用程序的步骤概述&#xff1a; 项目设置&#xff1a; 使用Qt Creator创建一个新的Qt Widgets Application或Qt Quick Application项目。 用户界面设计&#xff1…

Android Viewpager2 remove fragmen不生效解决方案

一、介绍 在如今的开发过程只&#xff0c;内容变化已多单一的fragment&#xff0c;变成连续的&#xff0c;特别是以短视频或者直播为主的场景很多。从早起的Viewpage只能横向滑动&#xff0c;到如今的viewpage2可以支持横向或者竖向滑动。由于viewpage2的adapter在设计时支持缓…

vue学习day09-自定义指令、插槽

29、自定义指令 &#xff08;1&#xff09;概念&#xff1a;自己定义的指令&#xff0c;可以封装一些dom操作&#xff0c;扩展额外的功能。 &#xff08;2&#xff09;分类&#xff1a; 1&#xff09;全局注册 2&#xff09;局部注册 3&#xff09;示例&#xff1a; 让表…

CV07_深度学习模块之间的缝合教学(2)--维度转换

教学&#xff08;1&#xff09;&#xff1a;链接 1.1 预备知识 问题&#xff1a;假如说我们使用的模型张量是三维的&#xff0c;但是我们要缝合的模块是四维的&#xff0c;应该怎么办&#xff1f; 方法&#xff1a;pytorch中常用的函数&#xff1a;(1)view函数&#xff08;2…

C++基础(三)

1.再探构造函数 之前的构造函数&#xff0c;初始化成员变量主要使用函数体内赋值&#xff0c;构造函数初始化还有一种方式&#xff0c;就是初始化列表&#xff0c;初始化列表的使用方式是以一个冒号开始&#xff0c;接着是一个以逗号分隔开的数据成员列表&#xff0c;每个“成…

系统架构师考点--软件工程(上)

大家好。今天我来总结一下软件工程的相关考点。这部分是考试的重点。在上午场客观题、下午场案例题以及下午场论文都有可能考到&#xff0c;在上午场客观题中大约占12-15分左右。 一、软件工程概述 软件开发生命周期 软件定义时期&#xff1a;包括可行性研究和详细需求分析过…

3d导入模型后墙体变成黑色?---模大狮模型网

在展览3D模型设计领域&#xff0c;技术和设计的融合通常是创意和实现之间的桥梁。然而&#xff0c;有时设计师们会遇到一些技术上的挑战&#xff0c;如导入3D模型后&#xff0c;墙体却突然变成了黑色。这种问题不仅影响了设计的视觉效果&#xff0c;也反映了技术应用中的一些复…

数据结构(4.4)——求next数组

next数组的作用:当模式串的第j个字符失配时&#xff0c;从模式串的第next[j]的继续往后匹配 求模式串的next数组(手算) next[1] 任何模式串都一样&#xff0c;第一个字符不匹配时&#xff0c;只能匹配下一个子串&#xff0c;因此&#xff0c;往后&#xff0c;next[1]都无脑写…

数据库学习作业

使用mysgldump命令备份数据库中的所有表 备份booksDB数据库中的books表 使用mysgldump备份booksDB和test数据库(test数据库自行准备) 使用mysq1命令还原第二题导出的book表 进入数据库使用source命令还原第二题导出的book表 创库&#xff0c;建表 建表的结果 插入数据 使用mysg…

医院同步时钟,构建医院零误差时间环境

在医院这个分秒必争、责任重大的场所&#xff0c;时间的准确性和一致性至关重要。医院同步时钟的应用&#xff0c;为构建医院零误差时间环境提供了坚实的保障。 一、医院同步时钟应用原因 首先&#xff0c;医疗工作的精确性和协同性依赖于统一且准确的时间。从手术的安排到药物…

MySQL篇:事务

1.四大特性 首先&#xff0c;事务的四大特性&#xff1a;ACID&#xff08;原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性&#xff09; 在InnoDB引擎中&#xff0c;是怎么来保证这四个特性的呢&#xff1f; 持久性是通过 redo log &#xff08;重做日志&…

解析CQRS架构模式

在日常开发过程中&#xff0c;关于如何正确划分操作的边界和职责一直是我们需要考虑的一个核心问题。针对这个问题&#xff0c;业界也诞生了一些新的设计思想和开发模式&#xff0c;其中最具代表性的就是今天要介绍的CQRS。 CQRS的全称是Command Query Responsibility Segregat…

图——图的遍历(DFS与BFS)

前面的文章中我们学习了图的基本概念和存储结构&#xff0c;大家可以通过下面的链接学习&#xff1a; 图的定义和基本术语 图的类型定义和存储结构 这篇文章就来学习一下图的重要章节——图的遍历。 目录 一&#xff0c;图的遍历定义&#xff1a; 二&#xff0c;深度优先…

【java计算机毕设】网上购书管理系统MySQL servlet JSP项目设计源代码 期末寒暑假作业 小组作业

目录 1项目功能 2项目介绍 3项目地址 1项目功能 【java计算机毕设】网上购书管理系统MySQL servlet JSP项目设计源代码 期末寒暑假作业 小组作业 2项目介绍 系统功能&#xff1a; servlet网上购书管理系统包括管理员、用户两种角色。 管理员功能包括订单管理&#xff08;已…

前端Vue组件化实践:自定义加载组件的探索与应用

在前端开发领域&#xff0c;随着业务逻辑复杂度的提升和系统规模的不断扩大&#xff0c;传统的开发方式逐渐暴露出效率低下、维护困难等问题。为了解决这些挑战&#xff0c;组件化开发作为一种高效、灵活的开发模式&#xff0c;受到了越来越多开发者的青睐。本文将结合实践&…

MySQL下载安装使用教程图文教程(超详细)

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们使…