首页 > 编程语言 >Java笔记之HashMap

Java笔记之HashMap

时间:2024-07-14 17:27:30浏览次数:15  
标签:hash HashMap 笔记 value key Entry Java 数组

HashMap的实现原理
HashMap概述

HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

2、HashMap实现存储和读取

1)存储

public V put(K key, V value) {
    // HashMap允许存放null键和null值。
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
    if (key == null)
        return putForNullKey(value);
    // 根据key的keyCode重新计算hash值。
    int hash = hash(key.hashCode());
    // 搜索指定hash值在对应table中的索引。
    int i = indexFor(hash, table.length);
    // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            // 如果发现已有该键值,则存储新的值,并返回原始值
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 如果i索引处的Entry为null,表明此处还没有Entry。
    modCount++;
    // 将key、value添加到i索引处。
    addEntry(hash, key, value, i);
    return null;
}
根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。

static int hash(int h) {
     h ^= (h >>> 20) ^ (h >>> 12);
     return h ^ (h >>> 7) ^ (h >>> 4);
}
我们可以看到在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率。

根据上面 put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

通过这种方式就可以高效的解决HashMap的冲突问题。

2)读取

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
        e != null;
        e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}
从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

总结:HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

3、HashMap的resize

当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小* loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16* 0.75=12的时候,就把数组的大小扩展为2* 16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

总结:HashMap的实现原理:

利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

标签:hash,HashMap,笔记,value,key,Entry,Java,数组
From: https://blog.csdn.net/m0_73363779/article/details/140381313

相关文章

  • Java笔记之ThreadLocal
    定义:ThreadLocal叫做线程变量,该变量对其他线程而言是隔离的,是当前线程独有的变量。ThreadLocal为变量在每一个线程中都创建了一个副本,并且该副本只能当前Thread使用,因此不存在多线程共享的问题。原理:Thread类有一个类型ThreadLocalMap的实例变量ThreadLocals,每个线程都有一个......
  • java总结第二周
    本周对JAVA的while,switch,for以及数组进行了学习。数组是一种数据结构,它可以存储一系列相同类型的变量。在Java中,定义一个数组需要指定其数据类型和大小。数组的索引从0开始,最后一个元素的索引是数组长度减1。可以通过索引来访问和修改数组中的元素。数组的主要优点是可以方便地......
  • 《项目管理》-笔记2
    1.项目集定义项目集是一组相互关联且被协调管理的项目、子项目集和项目集活动,以便获得分别管理所无法获得的利益。项目集有三个核心特征:(1)多个项目(2)统一战略目标(3)统一配置资源项目组合管理利用了MPT的概念,并且也应用了三个关键评估标准来衡量项目:项目承担的成本、存在的风险......
  • Java计算机毕业设计个性化旅游景点推荐网站(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在旅游业蓬勃发展的今天,随着人们生活水平的提高和休闲时间的增加,个性化旅游需求日益凸显。传统旅游推荐方式往往基于热门景点或固定线路,难以满足游客......
  • Factory method 'redissonClient' threw exception; nested exception is java.lang.I
    你遇到的这个错误是在Spring框架中常见的,它表示在创建Bean的过程中,有一个依赖关系未能得到满足。在这个特定的情况下,错误发生在创建voucherOrderController和voucherOrderServiceImpl这两个Bean时,其根本原因是无法实例化redissonClient,而redissonClient的创建失败是因为提供的Redi......
  • Java计算机毕业设计多媒体素材管理库(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,多媒体素材在教育教学、广告宣传、影视制作等多个领域的应用日益广泛。然而,多媒体素材的种类繁多、数量庞大,如何高效地存储、......
  • Java练习
    【例3.1】创建主类并调用其主方法在Eclipse下依次创建项目item、包Number和类First。在类体中输入以下代码,实现在控制台上输出"你好Java" 【例3.2】根据身高体重计算BMI指数创建BMlexponent类;声明double型变量height以记录身高,单位为米;声明int型变量weigbl以记录体重......
  • 003java jsp SSM在线医院医疗服务系统医院预约挂号医生坐诊健康资讯(源码+文档+开题+运
     项目技术:SSM+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:windows7/8/1......
  • 1117java jsp SSM Springboot在线答疑系统学生考试问题发布教师疑难解答(源码+文档+PPT
     项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:window......
  • 014java jsp SSM乡镇自来水收费系统水价水表管理(源码+文档+PPT+开题+任务书+运行视频+
     项目技术:SSM+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:windows7/8/1......