转载自 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 的内部原理有了更多了解。

Java 8 HashMap键与Comparable接口相关推荐

  1. JAVA——自定义排序(实现Comparable接口)

    Arrays.sort()方法可对任何实现compareble接口的对象数组排序. 源代码 EmployeeSortTest.java import java.util.*;/*** This pro ...

  2. java compareto 返回值_Java comparable接口及compareTo返回值所决定的升序降序问题

    我们在学习java基础的时候知道,基本数据类型数组可以直接调动Arrays类的静态sort方法,然后输出. 例如: int iArr[] = {1,2,4,6};  Arrays.sort(iArr) ...

  3. 简介------Comparable接口介绍及冒泡排序

    简单排序 在程序中,排序是常见的一种需求,提供一些数据元素,把这些数据元素按照一定的规则排序. 在java的开发工具包jdk中,已经给我们提供了很多数据结构与算法的实现,比如List,Set,Map, ...

  4. Java集合(7)--Map接口的实现类HashMap、LinkHashMap、TreeMap和Properties

    文章目录 HashMap类 LinkedHashMap类 TreeMap类 Hashtable类 Properties类 HashMap类 1.HashMap类概述 HashMap是 Map 接口使用 ...

  5. Java基础学习——Java集合(九)Map接口、HashMap、LinkedHashMap实现类、TreeMap实现类

    一.Map接口 二.HashMap实现类.LinkedHashMap实现类 1.HashMap的特点 1)无序,唯一(key唯一,因为底层key按照哈希表(数组+链表)的结构) 2)放入集合的数据的类 ...

  6. Java基础之Comparable接口和Comparator接口的比较

    前言 就是普普通通的写这么一篇文章,java集合类估计java程序猿都知道,那就写一点小众的. 在实际应用中,我们往往有需要比较两个自定义对象大小的地方.而这些自定义对象的比较,就不像简单的整型数据那 ...

  7. 011_TreeMap对键实现了Comparable接口的对象排序

    1. TreeMap对键实现了Comparable接口的对象排序 import java.util.TreeMap; import java.util.Map.Entry;/*** TreeMap对键 ...

  8. java comparable接口_Java面试题之Java集合篇三

    Java面试题之Java集合篇三1.HashMap和HashTable有何不同? (1)HashMap允许key和value为null,而HashTable不允许. (2)HashTable是同步的, ...

  9. Java中的冒泡排序,Comparator接口和Comparable接口的简单使用

    冒泡排序 冒泡排序是一种常见的排序方法,按照一定的规则(比如从小到大或者从大到小的顺序)对一组数据进行排序.而在Java开发中,也经常用到冒泡排序.我们就以下面的一个例子来讲解冒泡排序算法. 给定一个 ...

最新文章

  1. 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)
  2. access自动自动累计余额_【大招来临】—— 真 自动化财务报告
  3. 使用java调用fastDFS客户端进行静态资源文件上传
  4. PLSQL不能选择数据库问题
  5. C#数据结构与算法揭秘17
  6. 五天面试 Google、Facebook、Airbnb 等硅谷五家顶级公司,我是如何都拿到 Offer 的
  7. JDBC学习笔记——事务、存储过程以及批量处理
  8. jQuery零基础入门——(三)层级选择器
  9. nginx一键安装脚本
  10. oracle中locate,Oracle定位不知源头的SQL
  11. html5坦克游戏ppt说明,HTML5制作的坦克游戏
  12. Altium Designer(九):DXF+DWG
  13. Navicat for Mysql 如何备份数据库
  14. 安全测试-Drozer安全测试框架实践记录篇
  15. WPS office 下载
  16. vivado 下载bit报错End of startup status:LOW
  17. win7录屏_win7/win10屏幕录制教程?
  18. 电脑进程说明,常见,作用,说明,是否,查看,问题
  19. xposed+justtrustme
  20. CAD的高程注记转成Arcgis点要素(且带高程属性)

热门文章

  1. [Java基础]反射案列
  2. 前缀和优化+计蒜客 泡咖啡
  3. 计算机操作系统第四章作业
  4. linux权限746,linux文件权限学习笔一
  5. Mac(OS X)使用brew安装软件
  6. HDU 6750 Function(莫比乌斯反演)(2020百度之星初赛1)
  7. OpenJudge:熄灯问题
  8. #530. 「LibreOJ β Round #5」最小倍数 二分 + 数论
  9. 【CF1189D】Add on a Tree【结论】【构造】
  10. gym103117L. Spicy Restaurant