`
deepinmind
  • 浏览: 451451 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41622
社区版块
存档分类
最新评论

Java 8:HashMap的性能提升

阅读更多

HashMap<K, V>是一个高效通用的数据结构,它在每一个Java程序中都随处可见。先来介绍些基础知识。你可能也知道,HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶里。桶的数量通常要比map中的记录的数量要稍大,这样每个桶包括的值会比较少(最好是一个)。当通过key进行查找时,我们可以在常数时间内迅速定位到某个桶(使用hashCode()对桶的数量进行取模)以及要找的对象。

这些东西你应该都已经知道了。你可能还知道哈希碰撞会对hashMap的性能带来灾难性的影响。如果多个hashCode()的值落到同一个桶内的时候,这些值是存储到一个链表中的。最坏的情况下,所有的key都映射到同一个桶中,这样hashmap就退化成了一个链表——查找时间从O(1)到O(n)。我们先来测试下正常情况下hashmap在Java 7和Java 8中的表现。为了能完成控制hashCode()方法的行为,我们定义了如下的一个Key类:

class Key implements Comparable<Key> {
private final int value;
Key(int value) {
this.value = value;
}
@Override
public int compareTo(Key o) {
return Integer.compare(this.value, o.value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
Key key = (Key) o;
return value == key.value;
}
@Override
public int hashCode() {
return value;
}
}


Key类实现得相当不错:它重写了equals()并且提供了一个不错的hashCode()方法。为了避免过度的GC,我将不可变的Key对象缓存了起来,而不是每次都重新开始创建一遍:

class Key implements Comparable<Key> {
public class Keys {
public static final int MAX_KEY = 10_000_000;
private static final Key[] KEYS_CACHE = new Key[MAX_KEY];
static {
for (int i = 0; i < MAX_KEY; ++i) {
KEYS_CACHE[i] = new Key(i);
}
}
public static Key of(int value) {
return KEYS_CACHE[value];
}
}


现在我们可以开始进行测试了。我们的基准测试使用连续的Key值来创建了不同的大小的HashMap(10的乘方,从1到1百万)。在测试中我们还会使用key来进行查找,并测量不同大小的HashMap所花费的时间:

import com.google.caliper.Param;
import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;
public class MapBenchmark extends SimpleBenchmark {
private HashMap<Key, Integer> map;
@Param
private int mapSize;
@Override
protected void setUp() throws Exception {
map = new HashMap<>(mapSize);
for (int i = 0; i < mapSize; ++i) {
map.put(Keys.of(i), i);
}
}
public void timeMapGet(int reps) {
for (int i = 0; i < reps; i++) {
map.get(Keys.of(i % mapSize));
}
}
}




有意思的是这个简单的HashMap.get()里面,Java 8比Java 7要快20%。整体的性能也相当不错:尽管HashMap里有一百万条记录,单个查询也只花了不到10纳秒,也就是大概我机器上的大概20个CPU周期。相当令人震撼!不过这并不是我们想要测量的目标。

假设有一个很差劲的key,他总是返回同一个值。这是最糟糕的场景了,这种情况完全就不应该使用HashMap:

class Key implements Comparable<Key> {
//...
@Override
public int hashCode() {
return 0;
}
}





Java 7的结果是预料中的。随着HashMap的大小的增长,get()方法的开销也越来越大。由于所有的记录都在同一个桶里的超长链表内,平均查询一条记录就需要遍历一半的列表。因此从图上可以看到,它的时间复杂度是O(n)。

不过Java 8的表现要好许多!它是一个log的曲线,因此它的性能要好上好几个数量级。尽管有严重的哈希碰撞,已是最坏的情况了,但这个同样的基准测试在JDK8中的时间复杂度是O(logn)。单独来看JDK 8的曲线的话会更清楚,这是一个对数线性分布:



为什么会有这么大的性能提升,尽管这里用的是大O符号(大O描述的是渐近上界)?其实这个优化在JEP-180中已经提到了。如果某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD = 8),HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。它是如何工作的?前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。

这个性能提升有什么用处?比方说恶意的程序,如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)。JDK 8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击,同时也让HashMap性能的可预测性稍微增强了一些。我希望这个提升能最终说服你的老大同意升级到JDK 8来。

测试使用的环境是:Intel Core i7-3635QM @ 2.4 GHz,8GB内存,SSD硬盘,使用默认的JVM参数,运行在64位的Windows 8.1系统 上。

原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接

8
6
分享到:
评论
7 楼 3GQQ2012 2014-04-25  
white_crucifix 写道
white_crucifix 写道
evanzzy 写道
HashMap最傻的避免碰撞的办法就是在初始化的时候调整容量值,默认是0.75,调整这个值,对碰撞几率大的HashMap有很好的效果。如果HashMap的容量值能够动态调整,那么性能可以提高的更多。


桶的数量和Map的容量不是一回事吧


但应该是有些帮助,不过不至于太明显吧

我没理解错的话,hash值一样会落在同一个桶里,这个是跟有个桶没有啥关系吧?当hash值一样的时候都在一个桶里,桶的数量、容量影子如何能影响呢?
6 楼 white_crucifix 2014-04-25  
white_crucifix 写道
evanzzy 写道
HashMap最傻的避免碰撞的办法就是在初始化的时候调整容量值,默认是0.75,调整这个值,对碰撞几率大的HashMap有很好的效果。如果HashMap的容量值能够动态调整,那么性能可以提高的更多。


桶的数量和Map的容量不是一回事吧


但应该是有些帮助,不过不至于太明显吧
5 楼 white_crucifix 2014-04-25  
evanzzy 写道
HashMap最傻的避免碰撞的办法就是在初始化的时候调整容量值,默认是0.75,调整这个值,对碰撞几率大的HashMap有很好的效果。如果HashMap的容量值能够动态调整,那么性能可以提高的更多。


桶的数量和Map的容量不是一回事吧
4 楼 3GQQ2012 2014-04-25  
evanzzy 写道
HashMap最傻的避免碰撞的办法就是在初始化的时候调整容量值,默认是0.75,调整这个值,对碰撞几率大的HashMap有很好的效果。如果HashMap的容量值能够动态调整,那么性能可以提高的更多。

不明。。。容量值只是对扩容的时候才有作用吧?如何能避免碰撞?
3 楼 evanzzy 2014-04-25  
HashMap最傻的避免碰撞的办法就是在初始化的时候调整容量值,默认是0.75,调整这个值,对碰撞几率大的HashMap有很好的效果。如果HashMap的容量值能够动态调整,那么性能可以提高的更多。
2 楼 deepinmind 2014-04-24  
bitray 写道
treemap能达到O(n)是最好情况了吧


二叉树平均来说应该是O(logn)的。
1 楼 bitray 2014-04-24  
treemap能达到O(n)是最好情况了吧

相关推荐

    java软件技术文档-深入java8的集合3:HashMap的实现原理.pdf

    HashMap 在 Java 8 中有以下几个关键特性: 1. **非同步**:HashMap 不是线程安全的,这意味着在多线程环境下,如果不采取同步措施,可能会出现数据不一致的问题。如果需要线程安全的哈希表,可以使用 ...

    java课件-HashMap

    同时,避免过多的哈希冲突也有助于提升HashMap的性能。 总的来说,HashMap是Java编程中不可或缺的数据结构,广泛应用于各种场景,如缓存、配置管理等。理解其工作原理和使用注意事项,对于编写高效的Java代码至关...

    HashMap类.rar

    通过分析源码,开发者可以深入理解哈希表的工作原理,学习如何在易语言中实现高效的数据结构,这对于提升程序性能和优化内存管理至关重要。同时,这也为自定义数据结构或实现其他哈希表相关的功能提供了基础。

    hashmap.zip

    HashMap是Java编程语言中最常用的集合类之一,它提供了一种基于键值对(key-value pair)的数据存储方式,允许快速的插入、删除和查找操作。在HashMap 1.7版本中,我们主要关注以下几个核心知识点: 1. **内部结构*...

    在Java8与Java7中HashMap源码实现的对比

    本篇文章将深入对比Java 7和Java 8中HashMap的源码实现,探讨它们在解决哈希冲突和优化性能上的差异。 一、HashMap的基本原理 HashMap基于哈希表实现,它使用键的哈希值来决定键值对在数组中的位置。哈希表通过计算...

    HashMap的工作原理Java开发Java经验技巧共4页

    7. JDK版本差异:不同版本的Java可能对HashMap的实现细节有所调整,比如从JDK 8开始,HashMap使用了红黑树来优化长链表的情况,进一步提高了性能。 了解HashMap的工作原理后,开发者可以更好地选择何时使用HashMap...

    Generic-HashMap-Java:Java的通用HashMap

    在Java编程语言中,HashMap是一种常用的集合类,用于存储键值对数据。它实现了Map接口,提供了快速的插入、删除和查找操作。本项目“Generic-HashMap-Java”旨在提供一个泛型化的HashMap实现,以增强类型安全性并...

    hashMap具体详解

    哈希映射(HashMap)是Java编程语言中一个非常重要的数据结构,主要用于...了解并熟练掌握HashMap的原理和使用,对于提升Java程序的性能至关重要。在实际开发中,根据具体需求选择合适的数据结构是优化代码性能的关键。

    深入解读大厂java面试必考点之HashMap全套学习资料

    HashMap是Java编程语言中最常用的集合类之一,尤其在面试中,HashMap的相关知识是考察候选人对数据结构和算法理解的重要部分。本套学习资料全面涵盖了HashMap的深入解析,旨在帮助求职者掌握大厂面试中的核心知识点...

    Java performance

    《Java Performance》是一本专注于Java程序性能优化的专业书籍,它为开发者提供了深入理解并提升Java应用程序性能的关键知识。这本书详细探讨了如何在Java开发中识别和解决性能问题,旨在帮助开发者构建更快、更高效...

    hashmap面试题_hashmap_

    总结,HashMap是Java编程中的基础工具,掌握其工作原理和常见面试题,不仅能帮助我们应对面试,更能提升在实际开发中的问题解决能力。深入理解HashMap,有助于我们更好地利用数据结构,提高代码的执行效率。

    hashmap_use_in_JDK7.zip

    同时,也需要注意在不同版本的JDK中,HashMap的实现可能会有所变化,例如JDK8引入了红黑树来进一步优化链表过长的情况,提高了性能。 在实际开发中,根据需求选择合适的数据结构,比如对线程安全有要求时选择`...

    java面试 集合中知识点 HashMap(JDK1.8)源码+底层数据结构分析 整理.pdf

    红黑树是一种自平衡二叉查找树,它的插入、删除和查找操作的时间复杂度可以达到O(logn),这显著提升了在高冲突率场景下的性能。 **HashMap的源码分析**: - **构造方法**:HashMap的构造函数允许用户指定初始容量...

    20-集合框架020-HashMap-1080P 高清-AVC20

    Java 8之后,HashMap在链表长度达到一定阈值时,会自动将链表转换为红黑树,进一步优化查找效率。 HashMap的主要特点包括: 1. **非同步**:HashMap不是线程安全的,这意味着在多线程环境中,如果不采取同步措施,...

    java中HashMap的原理分析

    HashMap是Java编程中不可或缺的数据结构,它提供了高效的键值对存储和检索能力。在深入探讨HashMap的原理之前,我们先来澄清几个基本概念。 1. **哈希码(Hash Code)**:HashMap依赖于对象的哈希码来进行快速查找...

    你真的懂大厂面试题:HashMap吗?

    在Java编程语言中,HashMap是集合框架中一个重要的类,常用于存储键值对的数据结构。面试中,HashMap是常见的考察点,特别是对于大公司的面试。...在实际开发中,合理使用HashMap可以有效提升程序的性能。

    求职宝典-Java 基础面试题

    在求职过程中,掌握Java基础知识,尤其是核心数据结构如HashMap和HashTable的理解,对于面试至关重要。以下将详细介绍这两个类以及相关面试知识点。 HashMap和HashTable是Java中两种常用的基于哈希表的数据结构,...

    Java程序性能优化 让你的Java程序更快、更稳定

    Java程序性能优化是每个开发人员都需要关注的重要领域,它涵盖了多个方面,旨在提高代码执行效率,减少资源消耗,以及提升应用程序的稳定性和响应速度。在本文中,我们将深入探讨Java性能优化的关键点,帮助你的Java...

    Java程序性能优化 让你的Java程序更快、更稳定pdf文档视频资源

    Java程序性能优化是每个开发人员都需要关注的重要领域,特别是在企业级应用中,高效稳定的Java程序能够带来显著的业务优势。本资源包含一个PDF文档和相关的视频教程,旨在帮助你提升Java程序的速度和稳定性。 首先...

    hashmapTest:来自java-performance.info的HashMap性能测试

    标题中的“hashmapTest”指的是一个关于Java中HashMap性能测试的项目或实验,源自java-performance.info这个网站。这个测试可能是为了分析和比较不同情况下HashMap的性能表现,包括插入、查找、删除等操作的速度以及...

Global site tag (gtag.js) - Google Analytics