- 浏览: 685889 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
masuweng:
写的详细
Java中的枚举 -
zmwxiaoming:
java unix时间戳转换 -
g21121:
lhq1013 写道请问 我通过什么方式可以获取到tomca ...
tomcat优化 -
lhq1013:
请问 我通过什么方式可以获取到tomcat的qps值?
tomcat优化 -
zengshaotao:
condition的测试代码有问题,一个await的线程醒来之 ...
Java并发之Condition与Lock
Map实现之HashMap(性能及算法)
- 博客分类:
- 我所了解的Java
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K, V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K, V>(hash, key, value, e); //进行判断,是否扩容 if (size++ >= threshold) resize(2 * table.length); }
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int) (newCapacity * loadFactor); }
/** * 将table中所有Entry元素转移至newTable */ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K, V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K, V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
图示中Entry下标经过方法indexFor(int h, int length)进行了重新计算,所以结果下标是有可能变化的。
容量(capacity):哈希表中容器的数量,初始容量只是哈希表在创建时的容量。
负载因子(load factor):哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了负载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的容器数量。
可以说容量与负载因子的合理设置与否直接影响着HashMap的性能指标,我们在初始化时可以指定这两个参数:
//capacity=32,load factor=0.75 Map map=new HashMap<String,String>(32,0.75f);
此时HashMap会调用重载构造方法进行初始化,重载构造函数源代码如下:
/** * 使用指定容量和负载因子构建一个空的HashMap实例 */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int) (capacity * loadFactor); table = new Entry[capacity]; init(); }
重载构造方法会根据我们指定的容量与负载因子进行了初始化操作。
我们通过以下几个实验来探究下容量与负载因子对HashMap实例性能的影响:
1)实验一
实验原理:创建几个HashMap实例,初始化时指定不同的容量大小,并且它们采用默认相同的负载因子,向每个HashMap实例中添加1000000个元素,计算总用时,每个实验进行一定次数,得出对比分析图。
实验结果:
图中蓝色部分代表指定较小容量值;图中红色部分代表指定中等容量值;图中绿色部分代表指定合理容量值;
实验局限性:实验过程中会与使用机器硬件及相关参数设置是否合理有关,而且实验过程中可能会出现垃圾收集导致返回时间较长的情况,所以需要更大范围与更多次数的对比分析。但实验的结果方向性是正确的,可以作为参考。
实验结论:从实验结果可以很直观的看出,当我们指定的HashMap实例大小越接近实际使用大小,HashMap的性能越好。所以在加载因子不变的情况下,指定更合理的大小值可以有效的提高HashMap实例的性能。
2)实验二
实验原理:创建几个HashMap实例,初始化时指定相同的容量大小,不同的负载因子,向每个HashMap实例中添加1000000个元素,计算总用时,每个实验进行一定次数,得出对比分析图。
实验过程:
(1)默认长度为16,创建4个HashMap实例,它们的负载因子分别为0.25,0.5,0.75和1,代码如下:
Map map1 = new HashMap<String, String>(16,0.25f); Map map2 = new HashMap<String, String>(16,0.5f); Map map3 = new HashMap<String, String>(16,0.75f); Map map4 = new HashMap<String, String>(16,1f);
(2)不断向几个实例中填充元素,得到实验结果:
图中几条曲线是指定初始化容量较小时,不同负载因子对实例性能的影响,从中可以看出负载因子为0.5时这个HashMap的实例性能最好。负载因子为1时性能最差。
(3)指定HashMap实例初始容量为1232896,创建4个HashMap实例,它们的负载因子分别为0.25,0.5,0.75和1,代码如下:
Map map1 = new HashMap<String, String>(1232896,0.25f); Map map2 = new HashMap<String, String>(1232896,0.5f); Map map3 = new HashMap<String, String>(1232896,0.75f); Map map4 = new HashMap<String, String>(1232896,1f);
(4)不断向几个实例中填充元素,得到实验结果:
图中几条曲线是指定初始化容量较大时,不同负载因子对实例性能的影响,从中可以看出负载因子为0.25时这个HashMap的实例性能最差,其他负载因子的性能差不多。
实验结果:当添加元素较少时可以使用默认负载因子,此时性能差异并不明显;当添加元素较多时,可以通过测试或估算取得较佳的负载因子值。
实验局限性:实验过程中受添加元素数量的影响较大,元素数量级越大加载因子影响越大。
实验结论: 测试受多方面因素影响,添加不同数量元素的情况下相同的负载因子在性能影响上并不一致,只有根据实际运用情况来合理配置容量与负载因子的乘积才会使HashMap实例的性能更优,无法判断的情况下以默认值为最佳。
综合结论:为了提高HashMap实例的性能,我们可以在初始化时估算出要添加元素的数量,从而指定足以容纳这些元素的HashMap容量数值,负载因子在元素个数不同的情况下对HashMap实例的影响较大,再未通过测试的情况下可以采用默认值(未必是最优值),否则将降低实例的性能。
HashMap中的相关计算方法
在HashMap实现中有一些特定的方法,诸如计算下标,计算hash值等,这些方法被用于特定的场合,虽算不上比较复杂的算法,但重要性不可小看。
1.indexFor方法
indexFor(int h, int length)方法根据指定hash值和数组长度计算出该hash值所对应数组下标。indexFor在获取、添加元素等方法中担任重要角色。
以下是indexFor方法的源代码:
/** * 根据hash值h与数组长度length计算下标位置 */ static int indexFor(int h, int length) { return h & (length - 1); }
核心代码只有一行,但是却不要小看它。indexFor采用的是“按位与”的方式来计算出下标位置,“&”的操作其实就是将十进制的数转换成二进制后两个二进制数按相同位进行“与”计算,相同值“与”计算后结果不变,不同值“与”计算后结果为0.
注意:因为下标是从0开始的,以长度16为例,下标范围为0-15,所以代码中使用的是length-1
以length=16为例,计算hash值为12,15,46,90的index值
几个hash值对应二进制表示为:
十进制hash值 | 二进制值 |
12 | 1100 |
15 | 1111 |
90 | 1011010 |
234 | 11101010 |
此时h & (length - 1)结果即为(以hash为12为例):
12 & 15:
结果:12
类似的其他结果如下:
十进制hash值 | 二进制值 | 计算结果 |
12 | 1100 | 12 |
15 | 1111 | 15 |
90 | 1011010 | 10 |
234 | 11101010 | 10 |
总结:indexFor方法采用“&”操作最大的优点就是在计算出下标位置的同时又注重了高效率。
2.hash方法
HashMap为什么要单独提供一个hash方法?这个hash方法又有什么不同?
以下是hash方法的源代码:
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
hash方法是对已给出hashCode值的补充,用于防止那些质量较差的hash函数。并且将此hash函数作为已给定的hashCode的一个补充,可以提高hash值的质量。hash质量的好坏是非常重要,
HashMap用2的次幂作为表的hash长度,这就容易产生冲突。注意:Null 的key的hash值总是0,即他在
table的索引值永远为0。
hash方法如何提高hash值的质量,通过以下实例就会很好的了解。
1)初始化一个HashMap实例,指定容量为16:
Map map=HashMap(16);
2)向此实例中添加几个元素,它们key的hashcode分别为12,28,44和92。
3)通过indexFor方法可以计算出该key所应存储的下标位置,如果只是通过key的hashcode来计算的话,得出结果为:
key的hashCode | 二进制值 | length-1二进制值 | 计算后下标位置 |
12 | 1100 | 1111 | 12 |
28 | 11100 | 1111 | 12 |
44 | 101100 | 1111 | 12 |
92 | 1011100 | 1111 | 12 |
4)从结果可以看出虽然每个key拥有不同的hashCode,但是通过计算后它们却被分配到了同一下标位置,这显而易见不是我们想要的结果。
原因就在于indexFor方法的设计思路是“按位与”,这样计算过程中无论是0&1还是1&0结果都是0.所以几个key的二进制值与15的二进制值(1111)进行“与”操作时,实际就相当于只计算了最后四位有效位(红色部分),最终出现了上述结果。
hash函数正是为了避免这一点:
key的hashCode | 二进制值 | hash计算后 | length-1二进制值 | 计算后下标位置 |
12 | 1100 | 1100 | 1111 | 12 |
28 | 11100 | 11101 | 1111 | 13 |
44 | 101100 | 101110 | 1111 | 14 |
92 | 1011100 | 1011001 | 1111 | 9 |
这样就可以很好避免了冲突,以致于所添加的元素能够均匀的分布在整个table数组里,从而提高了HashMap的性能。
至此两篇关于HashMap的文章均已结束,希望能给朋友们带来一些帮助,如文章中出现那些错误还请雅正。
发表评论
-
maven自定义archetype
2018-06-25 15:30 1181在开发过程中我们经常会创建一系列结构类似的 ... -
Linux下安装Haproxy、Tomcat、Keepalived
2018-04-27 19:37 0首先介绍一下环境: 1) ... -
Linux下安装Haproxy、Nginx、Tomcat、Keepalived
2018-04-27 19:44 1520首先介绍一下环境: 1) ... -
Java中的枚举
2018-04-24 09:43 2336Enum(“枚举”)全称为 enumera ... -
Java中的枚举
2018-04-23 17:56 14Enum(“枚举 ... -
Java并发之Executor
2016-05-21 17:12 2666在很多系统 ... -
Java并发之Callable,Future,FutureTask
2016-05-18 18:31 0在传统的多线程实现方式中(继承Thread ... -
TOMCAT优化
2016-05-16 14:44 0Tomcat是我们经常使用的 servle ... -
Java并发之LinkedBlockingQueue
2016-05-12 13:34 0上一篇我们已经学习过了 ArrayBloc ... -
Java并发之LinkedBlockingQueue
2016-05-12 17:50 8488上一篇我们已经学习过了 ArrayBloc ... -
Java并发之ArrayBlockingQueue
2016-05-10 11:44 5214ArrayBlockingQueue是一个 ... -
Java并发之BlockingQueue
2016-05-09 21:32 1428一、Queue Queu ... -
Java并发之ReentrantLock
2016-05-05 19:48 2630java.util.concurrent. ... -
Java并发之ReentrantLock
2016-05-05 18:21 8java.util.concurrent. ... -
ReentrantLock
2016-05-03 10:28 0java.util.concurrent. ... -
Condition与Lock
2016-05-03 00:18 7java.util.concurr ... -
原子操作(CAS)
2016-05-02 16:41 15125众所周知锁有两种:乐观锁与悲观锁。独占锁是 ... -
Java并发机制locks之接口
2016-04-28 10:45 0... -
Java多线程进阶篇
2016-04-20 14:57 237通过前文我 ... -
Java多线程基础篇
2016-04-20 14:52 228在并发编程 ...
相关推荐
java 集合和泛型 1. Map接口 2. HashMap底层实现 3. Hash数据结构和算法 4. 红黑树数据结构和算法
get(key) -> get value associated with key set(key,value) -> set new element in map by key...map = HashMap({'a','b','c'},{1,2,3}); map.size map.has('d') map.delete('a'); map.has('a') map.size map.values()
本文即以Android平台为例来实现该算法。 具体代码如下: public static void main(String[] args) { Map<String> map = new HashMap(); map.put(lisi, 5); map.put(lisi1, 1); map.put(lisi2, 3); map.put...
HashMap和Hashtable的区别。 HashMap是Hashtable的轻量级实现(非...Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。 同步和异步有何异同,在什么情况下分别使用他们?举例说明。
主要介绍了Java8 HashMap扩容算法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
java hashmap 扩容因子为什么是0.75,官方给出的解释
一个简单而高效的Go的hashmap包,使用xxhash算法,开放寻址和robin hood散列。
1、此HashMap类采用java jdk中HashMap的实现方式 2、相比网站上发布过的hashtable之类的源码: 此HashMap寻址方法是拉链法.比开放寻址法对连续内存要求更低 哈希函数用的是java中String.hashCode()算法(经实际验证其...
Golang无锁线程安全的HashMap,为最快的读取访问进行了优化
Java用数组和链表的方式简单实现HashMap的增删改功能 数组和链表.pdf
HashMap和Hashtable的区别。 HashMap是Hashtable的轻量级实现(非线程安全的实现) ,他们都完成了Map接口 主要区别在于HashMap...Hashtable和HashMap采用的hash/rehash算法都大概一样,所 以性能不会有很大的差异。
看完这篇 HashMap,和面试官扯皮就没问题了 - HashMap 概述 - HashMap 和 HashTable 的区别 - 相同点 - 不同点 - HashMap 和 HashSet 的区别 - HashMap 底层结构 - AbstractMap 类 - Map 接口 - 重要内部类...
哈希图在C中的实现。特征使用哈希进行通用接口,支持可变大小的项目。 内置或并允许使用其他算法。 ANSI C(C99) 支持自定义分配器相当不错的表现。 :rocket:例子# include < stdio># include < string># include ...
HashMap和Hashtable的区别。 HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap...Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
原代码分析阈值插入删除查询关键下标的算法写在最后 什么是HashMap HashMap在代码的定义如下,从代码可以看出 HashMap的结构是一个数组。 Node[] table; static class Node implements Map.Entry { final int hash;...
它们都是像一样的简化实现 它使用改进的算法生成哈希。 这样可确保在所有铲斗上尽可能广泛地散布。 根据规范,基本Hashmap不能保证在迭代时满足插入顺序。 如果要在迭代时保证插入顺序,请使用LinkedHashMap 。 ...
面试必考之HashMap源码分析与实现 探索JVM底层奥秘ClassLoader源码分析与案例讲解 面试必备技能之Dubbo企业实战 分布式框架Zookeeper之服务注册与订阅 互联网系统垂直架构之Session解决方案 分库分表之后分布式下...
Java利用ConcurrentHashMap实现本地缓存demo; 基本功能有缓存有效期、缓存最大数、缓存存入记录、清理线程、过期算法删除缓存、LRU算法删除、获取缓存值等功能。 复制到本地项目的时候,记得改包路径哦~
4)了解Map接口及主要实现类(HashMap、TreeMap、HashTable) 二、实验内容及步骤 1、编写程序练习将以下5个Person类的对象放在一个HashSet中。 姓名:张三 身份证号:178880001 姓名:王五 身份证号:178880002 ...