Java7/8中的HashMap和ConcurrentHashMap全解析

1. Java7中的HashMap:

大方向上HashMap是一个数组,每个数组元素是一个单向链表。

上图中每个绿色的实体是嵌套类Entry的实例,Entry包含4个属性:key,value,hash,和单链表的next。

capacity:数组的容量,始终保持在2^n,每次扩容后大小为扩容前的2倍。

loadfactor:扩容因子,始终保持在0.75。

threshold:扩容的阈值,大小为:capacity*loadfactor。

1.1put方法的过程:

总结:

当第一次插入时需要初始化数组的大小(threshold);

判断如果key为空就将这个Entry放入到table[ 0 ]中;

否则计算key的hash值,遍历单链表,若该位置已有元素,就进行覆盖,并返回旧值;

若不存在重复的值,就将该Entry放入到链表中。

 1 public V put(K key, V value) {
 2     // 当插入第一个元素的时候,需要先初始化数组大小
 3     if (table == EMPTY_TABLE) {
 4         inflateTable(threshold);
 5     }
 6     // 如果 key 为 null,感兴趣的可以往里看,最终会将这个 entry 放到 table[0] 中
 7     if (key == null)
 8         return putForNullKey(value);
 9     // 1. 求 key 的 hash 值
10     int hash = hash(key);
11     // 2. 找到对应的数组下标
12     int i = indexFor(hash, table.length);
13     // 3. 遍历一下对应下标处的链表,看是否有重复的 key 已经存在,
14     //    如果有,直接覆盖,put 方法返回旧值就结束了
15     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
16         Object k;
17         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
18             V oldValue = e.value;
19             e.value = value;
20             e.recordAccess(this);
21             return oldValue;
22         }
23     }
24
25     modCount++;
26     // 4. 不存在重复的 key,将此 entry 添加到链表中,细节后面说
27     addEntry(hash, key, value, i);
28     return null;
29 }

1.2数组(大小)的初始化:

当第一个数组元素放入HashMap时,就进行一次数组的初始化,就是先计算数组的大小,再计算阈值(threshold),并始终将数组内元素的数量保持在2^n个。

 1 private void inflateTable(int toSize) {
 2     // 保证数组大小一定是 2 的 n 次方。
 3     // 比如这样初始化:new HashMap(20),那么处理成初始数组大小是 32
 4     int capacity = roundUpToPowerOf2(toSize);
 5     // 计算扩容阈值:capacity * loadFactor
 6     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
 7     // 算是初始化数组吧
 8     table = new Entry[capacity];
 9     initHashSeedAsNeeded(capacity); //ignore
10 }

1.3计算数组的位置:

根据key的Hash值,来对数组长度进行取模。eg:当数组长度为32时,可以取key的hash值的后5位,来进行计算相应数组中位置。

1.4添加结点到链表中:

找到数组下标后,进行key判重,若没有重复,就将该元素放到链表的表头位置。

以下方法首先判断是否需要扩容,如果扩容后,就将元素放到相应数组位置上链表的表头处。

 1 void addEntry(int hash, K key, V value, int bucketIndex) {
 2     // 如果当前 HashMap 大小已经达到了阈值,并且新值要插入的数组位置已经有元素了,那么要扩容
 3     if ((size >= threshold) && (null != table[bucketIndex])) {
 4         // 扩容,后面会介绍一下
 5         resize(2 * table.length);
 6         // 扩容以后,重新计算 hash 值
 7         hash = (null != key) ? hash(key) : 0;
 8         // 重新计算扩容后的新的下标
 9         bucketIndex = indexFor(hash, table.length);
10     }
11     // 往下看
12     createEntry(hash, key, value, bucketIndex);
13 }
14 // 这个很简单,其实就是将新值放到链表的表头,然后 size++
15 void createEntry(int hash, K key, V value, int bucketIndex) {
16     Entry<K,V> e = table[bucketIndex];
17     table[bucketIndex] = new Entry<>(hash, key, value, e);
18     size++;
19 }

1.5数组的扩容:

扩容就是将小数组扩大成大数组再将元素转移到大数组中。双倍扩容:比如原数组(每个数组中放的其实是一个链表)中old [ i]的元素,会放到新数组的new [ i] ,和new [ i+oldlength]的位置上。

 1 void resize(int newCapacity) {
 2     Entry[] oldTable = table;
 3     int oldCapacity = oldTable.length;
 4     if (oldCapacity == MAXIMUM_CAPACITY) {
 5         threshold = Integer.MAX_VALUE;
 6         return;
 7     }
 8     // 新的数组
 9     Entry[] newTable = new Entry[newCapacity];
10     // 将原来数组中的值迁移到新的更大的数组中
11     transfer(newTable, initHashSeedAsNeeded(newCapacity));
12     table = newTable;
13     threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
14 }

1.6 get过程的分析:

原文地址:https://www.cnblogs.com/xbfchder/p/11100468.html

时间: 2024-08-29 13:00:18

Java7/8中的HashMap和ConcurrentHashMap全解析的相关文章

Java并发指南13:Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 转自https://www.javadoop.com/post/hashmap#toc7 部分内容转自 http://www.jasongj.com/java/concurrenthashmap 今天发一篇"水文",可能很多读者都会表示不理解,不过我想把它作为并发序列文章中不可缺少的一块来介绍.本来以为花不了多少时间的,不过最终还是投入了挺多时间来完成这篇文章的. 网上关于 HashMap 和 Con

Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

转自:http://www.importnew.com/28263.html 今天发一篇”水文”,可能很多读者都会表示不理解,不过我想把它作为并发序列文章中不可缺少的一块来介绍.本来以为花不了多少时间的,不过最终还是投入了挺多时间来完成这篇文章的. 网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少,不过缺斤少两的文章比较多,所以才想自己也写一篇,把细节说清楚说透,尤其像 Java8 中的 ConcurrentHashMap,大部分文章都说不清楚.终归是希望能降低大

Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 (转)

阅读前提:本文分析的是源码,所以至少读者要熟悉它们的接口使用,同时,对于并发,读者至少要知道 CAS.ReentrantLock.UNSAFE 操作这几个基本的知识,文中不会对这些知识进行介绍.Java8 用到了红黑树,不过本文不会进行展开,感兴趣的读者请自行查找相关资料. Java7 HashMap HashMap 是最简单的,一来我们非常熟悉,二来就是它不支持并发操作,所以源码也非常简单. 首先,我们用下面这张图来介绍 HashMap 的结构. 这个仅仅是示意图,因为没有考虑到数组要扩容的情

深入剖析 Java7 中的 HashMap 和 ConcurrentHashMap

本文将深入剖析 Java7 中的 HashMap 和 ConcurrentHashMap 的源码,解析 HashMap 线程不安全的原理以及解决方案,最后以测试用例加以验证. 1 Java7 HashMap HashMap 的数据结构: 从上图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表. 通过查看 JDK 中的 HashMap 源码,可以看到其构造函数有一行代码: public HashMap(int initialCapacity, float loadFac

JDK1.8中的HashMap.HashTable, ConcurrentHashMap有什么区别?

JDK1.8中的HashMap,HashTable,ConcurrentHashMap有什么区别? 答:HashMap是线程不安全的,底层采用数组+链表+红黑树的结构 HashTable是线程安全的,因为使用了Synchronized锁住了整个table,底层采用了数组+链表 ConcurrentHashMap是线程安全的,采用了CAS+同步锁Synchronized对链表头节点进行锁定,底层使用数组+链表+红黑树 HashMap的key和value可以是null,其他两个不行. 原文地址:ht

Java7/8集合框架——HashMap

java.util.HashMap Java7/8中HashMap(和 ConcurrentHashMap)的相关基本操作源码介绍,这里可以直接参考[Java7/8中的 HashMap 和 ConcurrentHashMap 全解析],介绍得还是挺详细的,就不班门弄斧了. 关于Java7的HashMap,这里主要提几点,具体的分析还是请参考上面给的链接, Java7中HashMap源码的大方向就是:数组 + 单向链表(数组的元素,Entry 实例,包含四个属性:key, value, hash

Java7/8 中 HashMap 和 ConcurrentHashMap源码对比分析

网上关于 HashMap 和 ConcurrentHashMap 的文章确实不少,不过缺斤少两的文章比较多,所以才想自己也写一篇,把细节说清楚说透,尤其像 Java8 中的 ConcurrentHashMap,大部分文章都说不清楚.终归是希望能降低大家学习的成本,不希望大家到处找各种不是很靠谱的文章,看完一篇又一篇,可是还是模模糊糊. 阅读建议:四节基本上可以进行独立阅读,建议初学者可按照 Java7 HashMap -> Java7 ConcurrentHashMap -> Java8 Ha

HashMap、ConcurrentHashMap原理分析

集合(Collection)是编程中常用的数据结构,而并发也是服务器端编程常用的技术之一,并发总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).而Map这种以键值对为元素的数据结构也是集合中最常用到的.Map家族中的三大类:HashMap.HashTable.ConcurrentHashMap.前者非线程安全的,后两者是线程安全的,而HashTable的实现原理与HashMap很相似,只是在公开的方法上

HashMap与ConcurrentHashMap、HashTable

(1)HashMap的线程不安全原因一:死循环 原因在于HashMap在多线程情况下,执行resize()进行扩容时容易造成死循环. 扩容思路为它要创建一个大小为原来两倍的数组,保证新的容量仍为2的N次方,从而保证上述寻址方式仍然适用.扩容后将原来的数组从新插入到新的数组中.这个过程称为reHash. [单线程下的reHash]  扩容前:我们的HashMap初始容量为2,加载因子为1,需要向其中存入3个key,分别为5.9.11,放入第三个元素11的时候就涉及到了扩容. 第一步:先创建一个二倍