Java 8 HashMap键与Comparable接口

转载自 Java 8 HashMap键与Comparable接口

 

这篇文章主要介绍了 Java 8 在 HashMap 哈希冲突处理方面的新特性。

相对之前的版本,Java 8 在许多方面有了提升。其中有很多类被更新了,HashMap 作为最常使用的集合类之一也不例外。这篇文章将介绍 Java 8 中的 HashMap 在处理哈希冲突时的新特性。

让我们从头开始。最容易使 HashMap 发生哈希冲突的方法是什么呢?我们可以创建一个类,让它的哈希函数返回一个最糟糕的结果 —— 比如一个常数。这也是我在面试的时候经常问面试者的问题:哈希方法返回常数会造成什么结果?有很多次面试者会回答说 map 集合里会有且仅有一个元素,因为 map 中的旧元素总会被新的覆盖。这个回答当然是错误的。哈希冲突并不会导致 HashMap 覆盖一个已经存在于集合中的元素,这种情况只会在使用者试图向集合中放入两个元素,并且它们的键对于 equal() 方法是相等的时候才会发生。键不相等但又会产生哈希冲突的不同元素最终会以某种数据结构存储在 HashMap 的同一个桶中(注意,在这种情况下,因为插入和查找的操作都要耗费更长的时间,所以整体的性能就会受到影响)。

首先,我们用一个小程序来模拟哈希冲突。下面的写法可能比较夸张,因为它造成的冲突比现实中多得多,但这个程序对于证实哈希冲突的条件还是很重要的。

我们使用一个 Person 对象作为 map 的键,以字符串作为值。下面是 Person 的具体实现,有一个 firstName 字段,一个 lastName 字段和一个 ID 属性,其中 ID 属性以 UUID 对象表示。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class Person {

    private String firstName;

    private String lastName;

    private UUID id;

    public Person(String firstName, String lastName, UUID id) {

        this.firstName = firstName;

        this.lastName = lastName;

        this.id = id;

    }

    @Override

    public int hashCode() {

        return 5;

    }

    @Override

    public boolean equals(Object obj) {

        // ... pertty good equals here taking into account the id field...

    }

    // ...

}

现在我们可以开始制造一些冲突。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

private static final int LIMIT = 500_000;

private void fillAndSearch() {

     Person person = null;

     Map<Person, String> map = new HashMap<>();

 

     for (int i=0;i<LIMIT;i++) {

        UUID randomUUID = UUID.randomUUID();

        person = new Person("fn", "ln", randomUUID);

        map.put(person, "comment" + i);

     }

     long start = System.currentTimeMillis();

     map.get(person);

     long stop = System.currentTimeMillis();

     System.out.println(stop-start+" millis");

}

上面的代码在一台高性能计算机上运行了两个半小时。其中,最后的查找操作耗费了大约 40 毫秒。现在我们对 Person 类进行修改:使它实现 Comparable 接口,并且添加了下面的方法:

1

2

3

4

@Override

public int compareTo(Person person) {

    return this.id.compareTo(person.id);

}

再一次运行之前的程序,这一次在我的机器上它耗费的时间少于 1 分钟。其中,最终的查找操作耗费的时间接近为 0 毫秒 —— 比之前提高了 150 倍!

就像前面说的,Java 8 做了很多优化,其中也包括HashMap 类。在 Java 7 中,两个不同的元素,如果它们的键产生了冲突,那么会被存储在同一个链表中。而从 Java 8 开始,如果发生冲突的频率大于某一个阈值(8),并且 map 的容量超过了另一个阈值(64),整个链表就会被转换成一个二叉树。

原来如此!所以,对于没有实现 Comparable 的键,最终的树是不平衡的;而对于实现了 Comparable 的键,其二叉树就会是高度平衡的。事实是这样吗?不是。HashMap 内部是红黑树,也就是说它总是平衡的。我通过反射机制,查看了最终的树结构。对于拥有 50000 个元素(不敢让数字更大了)的 HashMap 来说,两种不同的情况下(实现或是不实现 Comparable 接口)树的高度都是 19 。

那么,为什么之前的实验结果会有那么大的差别呢?原因在于,当 HashMap 想要为一个键找到对应的位置时,它会首先检查新键和当前检索到的键之间是否可以比较(也就是实现了 Comparable 接口)。如果不能比较,它就会通过调用 tieBreakOrder(Object a,Object b) 方法来对它们进行比较。这个方法首先会比较两个键对象的类名,如果相等再调用 System.identityHashCode 方法进行比较。这整个过程对于我们要插入的 500000 个元素来说是很耗时的。另一种情况是,如果键对象是可比较的,整个流程就会简化很多。因为键对象自身定义了如何与其它键对象进行比较,就没有必要再调用其他的方法,所以整个插入或查找的过程就会快很多。值得一提的是,在两个可比的键相等时(compareTo 方法返回 0)的情况下,仍然会调用 tieBreakOrder 方法。

总而言之,在 Java 8 的 HashMap 里,如果一个桶里存放了大量的元素,它在达到阈值时就会被转换为一棵红黑树,对于实现了 Comparable 接口的键来说,插入或删除的操作会比没有实现 Comparable 接口的键更简单。通常,如果一个桶不会发生那么多次冲突的话,这整个机制不会带来多大的性能提升,但起码现在我们对 HashMap 的内部原理有了更多了解。

 

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

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

相关文章

hash地址_一致性Hash在负载均衡中的应用

作者&#xff1a;marklux原文&#xff1a;http://marklux.cn/blog/90简介一致性Hash是一种特殊的Hash算法&#xff0c;由于其均衡性、持久性的映射特点&#xff0c;被广泛的应用于负载均衡领域&#xff0c;如nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案。本文…

(转)linux上nginx源码编译安装

亲测有效&#xff1b; 转&#xff1a; https://segmentfault.com/a/1190000007116797https://segmentfault.com/a/1190000007116797 nginx服务器详细安装过程&#xff08;使用yum 和 源码包两种安装方式&#xff0c;并说明其区别&#xff09; 正在上传…重新上传取消​fuyi…

牛客网JAVA专项联系共899题--个人记录学习经历

总览 共刷900题 其中也有许多知识点是未曾涉足的&#xff0c;但大部分还是java的基础。 基本数据 正确题数&#xff1a;正确率百分之67&#xff0c;即&#xff1a;对了603题&#xff1b; 时间&#xff1a;5天&#xff08;每天4小时左右&#xff09; 错题&#xff1a; 收藏数…

ffmpeg中文开发手册_快速调用复杂命令,支持中文注释,命令行备忘工具navi两天就火了...

晓查 发自 凹非寺 量子位 报道 | 公众号 QbitAI刚学的一句新命令&#xff0c;才用完就忘了用法&#xff1f;通常情况下&#xff0c;命令后加一句—help就行了。但是&#xff0c;命令的帮助文档往往内容太太太太多了&#xff0c;在里面找到自己关心的部分实在太难了。查找出来的…

用python绘制图形_使用Python的turtle画炫酷图形

例子一&#xff1a; import turtle t turtle.Pen() turtle.bgcolor("black") sides6 colors["red","yellow","green","blue","orange","purple"] for x in xrang(360): t.pencolor(colors[x%sides]) t…

leetcode初级算法1.删除排序数组中的重复项

leecode初级算法1.删除排序数组中的重复项 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 class Solution {public int removeDuplicates(int[] nums) {int n nums.length;if (n 0) {return 0;}int fast 1, slow 1;while (fast < n) {if (nums[fast] ! num…

(转)mybatis热部署加载*Mapper.xml文件,手动刷新*Mapper.xml文件

转自&#xff1a; https://blog.csdn.net/LOVELONG8808/article/details/78738086 由于项目已经发布到线上&#xff0c;要是修改一个Mapper.xml文件的话&#xff0c;需要重启整个服务&#xff0c;这个是很耗时间的&#xff0c;而且在一段时间内导致服务不可用&#xff0c;严重…

图解HashMap和HashSet的内部工作机制

转载自 图解HashMap和HashSet的内部工作机制HashMap 和 HashSet 内部是如何工作的&#xff1f;散列函数&#xff08;hashing function&#xff09;是什么&#xff1f; HashMap 不仅是一个常用的数据结构&#xff0c;在面试中也是热门话题。 Q1. HashMap 如何存储数据&#xff1…

window location href 手机端无法跳转_Window对象在前端领域的角色

特殊的window提起window&#xff0c;在网页当中很常见&#xff0c;比如像这样&#xff1a;window.onloadfunction(){ //执行函数体 }这段代码的意思是当网页内容加载完成后要做什么。在js的领域&#xff0c;window对象有着双重角色&#xff0c;既是用来访问浏览器窗口的接口&am…

leetcode初级算法2.旋转数组

leecode初级算法2.旋转数组 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我自己的解法&#xff1a; public static void rotate(int[] nums, int k) {int move k % nums.length;Stack<Integer> stack new Stack<>();if(move 0){return;}else {f…

java运行环境变量及自定义变量

【README】 本文主要介绍java运行环境变量的获取&#xff0c;如何读取 env.properties 文件并将自定义变量写入到系统变量&#xff1b; 【1】System.getenv() 获取环境变量 public static void main1() {Map<String, String> envMap System.getenv();envMap.entrySet(…

Java HashSet和HashMap源码剖析

转载自 Java HashSet和HashMap源码剖析总体介绍 之所以把HashSet和HashMap放在一起讲解&#xff0c;是因为二者在Java里有着相同的实现&#xff0c;前者仅仅是对后者做了一层包装&#xff0c;也就是说HashSet里面有一个HashMap&#xff08;适配器模式&#xff09;。因此本文将重…

怎么把页面按比例缩小_meta viewport 是做什么用的,怎么写?

前置知识&#xff08;有助于viewport的理解&#xff09;李明&#xff1a;设备像素、设备独立像素、CSS像素、分辨率、PPI、devicePixelRatio​zhuanlan.zhihu.com移动端的问题屏幕窄&#xff0c;一般来说设备独立像素不超过400px。比如把网站侧边栏宽度设置为10%&#xff0c;这…

(转)java动态代理与aop

转自&#xff1a; Java 动态代理与AOP - 如果的事 - 博客园动态代理与AOP 代理模式 代理模式给某一个目标对象(target)提供代理对象(proxy)&#xff0c;并由代理对象控制对target对象的引用。 模式图&#xff1a; 代理模式中的角色有&#xff1a; 抽象对象角色(Abstrachttps://…

ConcurrentHashMap的红黑树实现分析

转载自 ConcurrentHashMap的红黑树实现分析红黑树 红黑树是一种特殊的二叉树&#xff0c;主要用它存储有序的数据&#xff0c;提供高效的数据检索&#xff0c;时间复杂度为O(lgn)&#xff0c;每个节点都有一个标识位表示颜色&#xff0c;红色或黑色&#xff0c;有如下5种特性&a…

leetcode初级算法3.存在重复元素

leetcode初级算法3.存在重复元素 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a; Arrays.sort(nums);for(int i 0; i < nums.length-1; i){if(nums[i] nums[i1]){return true;}}return false;官方题解&#xff1a; public boolean con…

sql server累计求和函数_SQL基础--SQL高级功能

一.窗口函数有什么用&#xff1f;在日常工作中&#xff0c;经常会遇到需要在每组内排名&#xff0c;比如下面的业务需求&#xff1a;排名问题&#xff1a;每个部门按业绩来排名topN问题&#xff1a;找出每个部门排名前N的员工进行奖励面对这类需求&#xff0c;就需要使用sql的高…

(转)【SpringMvc】如何使用form发送PUT和DELETE请求

转自&#xff1a; https://blog.csdn.net/cockroach02/article/details/82194126https://blog.csdn.net/cockroach02/article/details/82194126 一、当前现状 浏览器使用form提交信息的时候只支持GET和POST&#xff0c;如果需要在浏览器上使用PUT和DELETE请求方式的话&#…

leetcode初级算法4.只出现一次的数字

leetcode初级算法4.只出现一次的数字 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a; public static int singleNumber(int[] nums) {if(nums.length 1){return nums[0];}Arrays.sort(nums);int slow 0;int fast 1;while(fast < nums.…

集合总结(Collection)

转载自 集合总结(Collection) 最近项目上线完&#xff0c;闲来无事&#xff0c;整理了关于集合相关对比&#xff0c;具体详见以下几点&#xff1a;1.ArrayList和Vector区别&#xff1a;这两个类都实现了List接口(List接口继承了Collection接口)&#xff0c;他们都是有序集合&am…