分享好友 最新动态首页 最新动态分类 切换频道
hashmap排序_聊聊 HashMap
2024-12-27 02:10

面试的时候,当面试官问你 Java 基础,HashMap 可以说是一个绕不过去的话题,哪怕其他容器(比如 ArrayList,LinkedList)都不问,HashMap 也是不能不问的。不仅仅因为在平时工作中,HashMap 是一个很常用的数据结构,而且由 HashMap 这个数据结构其实能引出很多问题。比如最基本的 get()、put() 方法是吧,get() 就可以聊聊 equals() 方法跟 hashCode() 方法,这算是 Java 基础了吧;稍微升级一下难度,聊聊扩容过程,线程安全问题,进而引申到 ConcurrentHashMap,引申到多线程,引申到 Synchronized 关键字,引申到 JVM 虚拟机是吧,你看这样一联想,整个都串到一起了。

hashmap排序_聊聊 HashMap

哈希表在工作中是个很常用的数据结构,因为较好的兼顾了修改跟查找的性能。JDK1.0 中最早实现这个数据结构的类是 HashTable,而且更贴心的是这个类是线程安全的。理论上,只要想使用哈希表这个数据结构的的地方,用 HashTable 就肯定不会有问题。但是凡事都是有代价的,在实现了线程安全的同时,其实是极大地牺牲了性能的。简单翻一下 HashTable 的源码,看 get() 跟 put() 两个方法,可以看到这两个方法都被加了关键字,这也是 HashTable 线程安全的原因。

但是你想,直接把整个对象锁住,这是不是小题大做了,尤其是在单线程的场景下,就比如一个业务请求进来,在这个请求的处理过程中新建了一个哈希表,去对哈希表进行一些操作的时候,每次都上锁是不是过分了?其实就他一个线程操作,根本没必要去上锁,全程绿灯直接操作就行了。所以在 JDK1.2,我们有了一个新的类,HashMap。这个类并不是线程安全的,也意味着在单线程情况下,他没有为了保证线程安全而引入的不必要的开销,所以性能很好。于是,那个时候,没有线程安全要求的,直接使用 HashMap 就行了,有线程安全要求,比如某些全局变量什么的多线程访问的情景,那就直接使用 HashTable。

但其实前面我们也说了 HashTable 实现线程安全的方式十分简单粗暴,直接对整个哈希表加了锁,这就导致在高并发场景下频繁操作哈希表的时候,性能较差。于是在 JDK1.5 里面,并发大师 Doug Lea 为我们带来了 ConcurrentHashMap,通过粒度更细的锁,减少了锁的竞争,从而实现了更好的性能。

在不同版本的 JDK 中,HashMap 是在不停优化的。

  • 比如 JDK1.6 里面 new HashMap 时,开辟了内存空间,如果不使用,则浪费内存;JDK1.7 里面改成了懒加载,new HashMap 并没有开辟内存空间,节约了内存空间
  • 1.8 优化了 hashCode 算法:高 16 位异或(XOR)低 16 位 主要是增加了扰动,减少了哈希碰撞的几率
  • 1.8 里面优化了 hash 碰撞较多情况下的性能问题(链表长度限制为 8,过长时会将链表转换为 TreeNode(红黑树))
  • HashMap:它根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。

  • Hashtable:Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。

  • LinkedHashMap:LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

  • TreeMap:TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的 Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。

对于上述四种 Map 类型的类,要求映射中的 key 是不可变对象。不可变对象是指该对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map 对象很可能就定位不到映射的位置了。

其实广义来讲,数据结构只有数组跟链表两种,本质上就是看数据在存储的时候是否连续,其他的各种数据结构,比如栈、队列、跳表、树,都可以用数组或者链表来实现。而 HashMap,算是集中了这两者特点的一种数据结构,在很多地方都会用到。比如 Spring 里面存放各种 bean 用的容器,用的就是 ConcurrentHashMap。

 
  

上述代码摘自 JDK 1.8 中 HashMap 部分源码,table 即为我所说的数组,而 Node 即为链表的一个节点,里面有 hash、key、value 以及下一个节点。

下面分别介绍一下 Node 中的各个字段

  • hash:存储的键值对中 key 的 hash
  • key:存储的键值对中的 key
  • value:存储的键值对中的 value
  • next:指向下一个 Node
 
  

HashMap 容器初始化的容量为 16,默认负载因子为 0.75,树化阈值为 8,反树化阈值为 6,最小的树化容量为 64。

负载因子(LOAD_FACTOR)结合容量(CAPACITY),其实就得到了 HashMap 扩容的一个阈值,大于这个阈值的时候,HashMap 就会进行 resize() 扩容操作。至于负载因子为什么是 0.75,源码里的注释简单提了一嘴,其实就是不说我们也能猜出来。

As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs.  Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put).

其实就是权衡了空间占用跟查找效率,负载因子太高固然节省空间,但查找效率也会因此下降,包括 get 跟 set 等操作。

回到上边那张图,对于初始容量 16,负载因子 0.75 来说,超过 12,也就是图中画的 13 个元素,就会触发扩容,所以我前面说那张图有问题。但当时画的时候随手画的,也懒得改了,就随它去吧。

 
  

从上述算法可知,HashMap 中可以存放 key 为 null 的值,放在首位。而对于哈希值,是将高 16 位与低 16 位进行异或运算,这样做的目的主要是尽量保证 hash 值的均匀,通过这样一个扰动函数,减小了 hash 冲突的概率。

然后再通过这个 hash 值与数组的长度进行取模运算,决定这个键值对该放入 table 中的哪个 Node 中。

当容量大于阈值的时候,就会触发 HashMap 的扩容操作。扩容操作其实就是新建一个容量是原先数组两倍的新数组,将原先小数组的元素重新哈希到新的数组上。因为容量是 2 的幂,扩容的时候,元素要么在原位置,要么往后移动原来的容量大小的位置,这时候使用的是下面这个算法

e.hash & oldCap

如果得到的结果为零,则在原位置;反之,则将其向高位移动 oldCap 大小的位置。

教科书上告诉我们,碰到哈希冲突的时候,一般有两种解决办法开放定址法链表法

HashMap 选择的是链表法来解决哈希冲突的,也十分好理解。简单来说就是当哈希冲突的时候,将哈希冲突的元素挂到一条链表上,至于查找的时候具体是哪个元素,就需要再去链表上比较查出来了。

  • 对 key 的 hashCode() 做 hash 运算,计算 index
  • 如果没碰撞直接放到 table 里
  • 如果碰撞了,以链表的形式存在 table 后
  • 如果碰撞导致链表过长(大于等于 TREEIFY_THRESHOLD),就把链表转换成红黑树 (JDK1.8 中的改动)
  • 如果节点已经存在就替换 old value(保证 key 的唯一性)
  • 如果 table 满了(超过 load factor*current capacity),就要 resize。
  • 对 key 的 hashCode() 做 hash 运算,计算 index;
  • 如果在 table 里的第一个节点里直接命中,则直接返回
  • 如果有冲突,则通过 key.equals(k) 去查找对应的 Node
    • 若为树,则在树中通过 key.equals(k) 查找
    • 若为链表,则在链表中通过 key.equals(k) 查找,。

虽然源码中说了,根据泊松分布,哈希冲突的概率很低。但是如果真碰到一些极端情况,比如写死了 HashCode,那么那个 Node 上的元素过多,是会从链表转换成红黑树的。这里其实有一点就是并不是链表长度达到 8 就会转成红黑树。还有另外一个前提条件,就是整个哈希表容量要大于等于树化阈值,默认的树化阈值是 64,因为如果哈希表容量过小就用红黑树,可能会引起频繁扩容跟链表树化之间的冲突,性能并不一定比链表好。所以,在大容量的哈希表中,最极端的情况也就是通过红黑树来查找元素,这种情况下的算法复杂度是,明显好于链表的。

这里其实经常容易被问到的另一个问题就是:为什么用的是红黑树,而不是 AVL 树。

要回答这个问题就要回到这两种数据结构的特点上面。红黑树的查询性能略微逊色于 AVL 树,因为它比 avl 树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的 avl 树最多多一次比较,但是,红黑树在插入和删除上完爆 avl 树,avl 树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于 avl 树为了维持平衡的开销要小得多。所以,对于 HashMap 这种频繁读写的数据结构,选择红黑树相比 AVL 树是更好的选择。

最新文章
谷歌SEO优化,如何做好local business?
在当今数字化时代,谷歌搜索引擎已成为连接消费者与本地商家的重要桥梁。作为一名在SEO领域摸爬滚打多年的从业者,我深知在谷歌搜索结果中获得良好排名对于本地企业来说至关重要。这不仅意味着更多的曝光机会,还能直接转化为销售额的提升
外链建设计划:利用微信 Bug 提升网站权重
在竞争激烈的数字环境中,网站外链建设对于提高搜索引擎排名和网站可见度至关重要。利用微信 Bug 外链方法,我们可以有效地获得高质量外链,从而提升网站权重。本文将提供一个详细的外链建设规划计划和执行方案,帮助您有效地利用此策略。
除了您在Google搜索中看到的文字广告之外,展示广告网络中的网站还可以展示其他具有视觉吸引力的广告类型:文字广告 图片广告——包含照片或插图的广告富媒体广告—&m
上海天擎结合Google领先的搜索广告优化经验,为广告主定制出一套适合Google推广的解决方案MADA服务模式,通过市场分析策略、广告管理策略、数据分析策略、账户优化策略及服务保障策略全方位、专业、高效的满足企业Google推广的需求,真正
短剧逍遥全集观看_逍遥 电视剧
今天给各位分享短剧逍遥全集观看的知识,其中也会对逍遥 电视剧进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!1、总之,又见逍遥电视剧一共40集,这个集数安排既符合了电视剧制作的一般规律,又满足了观众对于精
十大耐玩平民手游多人下载推荐 耐玩的手机游戏有哪些2024
一些平民玩家很爱玩那种不肝不氪又很耐玩的手游,许多福利超多的联机类游戏都很适合平民体验,多元化内容加上良心福利机制满足了平民小白玩家的需求,那么十大耐玩平民手游多人下载推荐哪个好?本文介绍10个免费耐玩佳作都需要联网运行,平
转载:【AI系统】从 CUDA 对 AI 芯片思考
从技术的角度重新看英伟达生态,有很多值得借鉴的方面。本文将主要从流水编排、SIMT 前端、分支预测和交互方式等方面进行分析,同时对比 DSA 架构,思考可以从英伟达 CUDA 中借鉴的要点。英伟达生态的思考点从软件和硬件架构的角度出发,CU
新奥精准资料免费大全|精选解释解析落实
  在这个信息爆炸的时代,获取高质量的资料变得越来越重要。本文旨在提供一个全面的资料库,涵盖了各个领域的精选资源,从科技、人文到社会科学等,无需付费即可享有。我们将对每个领域的资料进行精选解释和深度解析,确保读者能够真正理
活动回顾 | “心航向”职业生涯规划工作坊
“心航向”职业生涯规划工作坊— 活 动 回 顾 —在职业道路的选择上,明确的方向和明智的规划是成功的关键。2024年12月7日,浙江大学心理与行为科学系在海纳苑3幢417室举办了一场意义深远的“心航向”职业生涯规划工作坊,旨在为心理系学子
看过《被困百万年:弟子遍布诸天万界》的人还看过
感谢您对我方的信任,我们将按照法律法规要求,采取严格的安全保护措施,保护您的个人隐私信息。在此,我们郑重的提醒您:1.在您使用我方提供的服务时,建议您详细阅读本用户隐私政策,详细了解我方收集,存储,使用,披露和保护您的个人信
美甲培训如何助力你在教育行业中脱颖而出?
作为一位教育行业从业者,我们都希望自己能够在竞争激烈的市场中脱颖而出。而美甲培训正是一个能够帮助我们实现这一目标的利器。在本文中,我们将从多个角度来阐述美甲培训如何助力我们在教育行业中取得成功。美甲培训不仅仅是教授学员如何
相关文章
推荐文章
发表评论
0评