Java8中HashMap扩容算法小计

Java8的HashMap扩容过程主要就是集中在resize()方法中

1 final Node<K,V>[] resize() {
2     // ...省略不重要的
3 }

其中,当HashMap扩容完毕之后,需要对原有的数据进行转移。因为容量变大了,部分元素的位置因此要变更,因而出现了下面的这个转移过程。

转移过程大致是:依次从旧数组里取值,然后从该值对应的链表上依次取出节点,对节点取模分别放入lo链表和hi链表,当链表中节点遍历完后,分别把lo链表和hi链表放入新数组的不同位置。

在看到如下第15行时,我在想,为什么(e.hash & oldCap)== 0时就放入lo链表,否则就是hi链表?

说到这个问题,那我们就要回顾下HashMap存入新元素的过程了。看下面的第45行,可以发现插入时是使用(n - 1) & hash来计算位置的,即数组长度-1,而扩容移位是使用数组长度n计算的,那这是为什么呢?

 1 for (int j = 0; j < oldCap; ++j) {
 2     Node<K,V> e;
 3     if ((e = oldTab[j]) != null) {
 4         oldTab[j] = null;
 5         if (e.next == null)
 6             newTab[e.hash & (newCap - 1)] = e;
 7         else if (e instanceof TreeNode)
 8             ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
 9         else { // preserve order
10             Node<K,V> loHead = null, loTail = null;
11             Node<K,V> hiHead = null, hiTail = null;
12             Node<K,V> next;
13             do {
14                 next = e.next;
15                 if ((e.hash & oldCap) == 0) {
16                     if (loTail == null)
17                         loHead = e;
18                     else
19                         loTail.next = e;
20                     loTail = e;
21                 }
22                 else {
23                     if (hiTail == null)
24                         hiHead = e;
25                     else
26                         hiTail.next = e;
27                     hiTail = e;
28                 }
29             } while ((e = next) != null);
30             if (loTail != null) {
31                 loTail.next = null;
32                 newTab[j] = loHead;
33             }
34             if (hiTail != null) {
35                 hiTail.next = null;
36                 newTab[j + oldCap] = hiHead;
37             }
38         }
39     }
40 }
41
42
43 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
44     // ...省略不重要的
45     if ((p = tab[i = (n - 1) & hash]) == null)
46         tab[i] = newNode(hash, key, value, null);
47     else {
48     // ...省略不重要的
49 }

像我们看Java8的HashMap源码,应该都应该知道HashMap的底层数组长度都是2的n方的值

那么我们就假设一个底层数组长度为8的HashMap模拟进行插入元素和扩容移位的过程

长度n=8 ---->    0x1000

n-1    ---->    0x0111

此时写入两个元素,两个元素的hash值分别为hash1 = 0x0101,hash2 = 0x1101

hash1 & n-1 = 0x0101

hash2  & n-1 = 0x0101

两个hash取模后的结果是一致的,所以它们会在同一个地方组成链表

那么此时如果要进行扩容移位呢?

hash1 & n = 0x0000

hash2 & n = 0x1000

此时两者的结果是不一样的,并且相差0x1000即10进制的8即数组长度.。

所以这也就是为什么上图15行只判断==0的原因,因为这个取模结果只有0和1两种值(数组长度是2的n次方,只有除了符号位外的最高位为1)

而两个取模结果等于数组长度,这也就是为什么上图第32和36行那么处理的原因。

原文地址:https://www.cnblogs.com/kumu/p/12079553.html

时间: 2025-01-13 09:32:58

Java8中HashMap扩容算法小计的相关文章

java8中hashMap

摘自:http://www.importnew.com/20386.html 简介 Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap.Hashtable.LinkedHashMap和TreeMap,类继承关系如下图所示: 下面针对各个实现类的特点做一些说明: (1) HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap最多只允许

算法小计-列表排列

算法的简单的概念算法的概念:O()大O表示法O(): 程序大概运行的次数 1,时间复杂度:时间复杂度是用来估计算法运行时间的一个式子(单位).常见的时间复杂度(按效率排序)O(1)<O(logn)<O(n)<O(nlogn)<O(n**2)<O(n**2logn)<O(n**3)前四种较重要. 2,空间复杂度:时间复杂度:用来评估算法内存占用大小的一个式子 列表排序:排序low b三人组:冒泡排序每两个相邻的数比较,如果对比一个数比一个大,交换位置.def Bubble

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

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

【算法导论学习-015】数组中选择第i小元素(Selection in expected linear time)

1.算法思想 问题描述:从数组array中找出第i小的元素(要求array中没有重复元素的情况),这是个经典的"线性时间选择(Selection in expected linear time)"问题. 思路:算法导论215页9.2 Selection in expect linear time 2.java实现 思路:算法导论216页伪代码 /*期望为线性时间的选择算法,输入要求,array中没有重复的元素*/ public static int randomizedSelect(i

用SQL实现统计报表中的“小计”和“合计”

问题: 开发一个关于各烟叶等级的二次验级的原发件数.原发重量及验收重量的统计报表.其中,原发件数.原发重量和验收重量等列要求计算出各等级组别的小计和所有记录的合计. 语句: SELECT DECODE(GROUPING(T4.TOBACCO_CLASS_TYPE) + GROUPING(T1.TOBACCO_CLASS_NAME), 1, DECODE(T4.TOBACCO_TYPE, 51, ‘上等烟小计’, 52, ‘中等烟小计’, 53, ‘下等烟小计’, 54, ‘低等烟小计’, ‘小计

设计模式小计——23种设计模式3

责任链模式Chain of Responsibility 使多个对象都有机会处理请求,从而避免请求的发送者和接受者间的耦合关系,并沿着这条链传递请求,直到有对象处理它为止 责任链模式关键是建立链接关系,在链中决定谁来处理请求 //抽象处理者 public abstract class Handle{ private Handle nextHandle;//链的下一个节点 public final Response handleMessage(Request request){ Response

Java中HashMap源码分析

一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtable大致相同)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Collections.sync

traits技法小计

在学习算法导论的时候,对于各数据结构,自然是实现一个才算掌握,工具当然是template编程,但是自己的demo经常存在很多问题,比如没有给出迭代器啊,操作符重载不够啊等等设计上的问题,而某些问题实际上是从设计之初就该考虑的大框架,而非小细节.对于C++而言,STL无疑是最佳的参考资料,侯捷先生的STL源码剖析一书给我们良好的示范,而直接从第四章开始看会云里雾里,无法得其精髓,因此在学习算法之余决定尾随侯捷先生脚步,学习STL traits技法,从而可以从STL中学到更多的数据结构实现. 收获自

设计模式小计——23种设计模式2

模板方法模式Template Method Pattern 定义一个操作的算法的框架,是的子类可以不改变算法结构即可重定义该算法一些特定步骤 public abstract class AbstractClass{//抽象模板类 protected abstract void method1();//算法步骤1 protected abstract void method2();//算法步骤2 public void templateMethod(){//模板方法,定义算法结构 this.met