本文继续分析HashMap的源码。本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见。

话不多说,咱们上源码。

final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //如果老的数组为空,老的数组容量设为0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0; //如果老的数组容量大于0,首先判断是否大于等于HashMap的最大容量,
//如果true,将阈值设置为Integer的最大值,同时数组容量不变
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
} //如果扩容后的数组容量小于我们规定的最大数组容量,而且老的数组容量大于等于16,
//对数组进行扩容,扩容后的数组容量为原来的两倍;同时阈值也扩容为原来的两倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
} //如果老的数组容量为0,而且老的阈值大于0,则新的容量=老的阈值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { //老的阈值=0,容量和阈值都初始化为默认值,即16和12
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
} //如果新的阈值为0,为新的阈值赋值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//首先定义一个新的容量的数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
//遍历老的数组
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
//如果链表中只有一个数据,直接重新计算hash值,放入新的数组中
newTab[e.hash & (newCap - 1)] = e;
//如果e是红黑树,需要将红黑树拆分后放入新的数组中
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

上面一段代码的内容比较好理解,都已经根据注释就能看懂,主要的内容在下半部分:扩容后和扩容前,数据存放位置的变化。我们可以理解一下。

经过观测可以发现,我们使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。看下图可以明白这句话的意思,n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。

小结

以上就是HashMap中比较重要的源码分析,希望大家能有所收获。高并发时,HashMap还有一些问题,具体是啥问题,大家搜一搜吧,后续可能会出相应的文章,届时再详细解析。所以,在高并发的情况下,还是尽量使用ConcurrentHashMap,后续也会对ConcurrentHashMap的源码进行解析,希望大家关注。

参考

JAVA源码分析-HashMap源码分析(二)的更多相关文章

  1. java集合系列之HashMap源码

    java集合系列之HashMap源码 HashMap的源码可真不好消化!!! 首先简单介绍一下HashMap集合的特点.HashMap存放键值对,键值对封装在Node(代码如下,比较简单,不再介绍)节 ...

  2. JAVA源码分析-HashMap源码分析(一)

    一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...

  3. Java集合系列[3]----HashMap源码分析

    前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...

  4. JDK源码解析---HashMap源码解析

    HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是 ...

  5. java集合中的HashMap源码分析

    1.hashMap中的成员分析 transient Node<K,V>[] table; //为hash桶的数量 /** * The number of key-value mapping ...

  6. 【源码】HashMap源码及线程非安全分析

    最近工作不是太忙,准备再读读一些源码,想来想去,还是先从JDK的源码读起吧,毕竟很久不去读了,很多东西都生疏了.当然,还是先从炙手可热的HashMap,每次读都会有一些收获.当然,JDK8对HashM ...

  7. 【Java集合学习】HashMap源码之“拉链法”散列冲突的解决

    1.HashMap的概念 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射. HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io ...

  8. java容器三:HashMap源码解析

    前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...

  9. java(8) HashMap源码

    系统环境: JDK1.7 HashMap的基本结构:数组 + 链表.主数组不存储实际的数据,存储的是链表首地址. 成员变量 //默认数组的初始化大小为16 static final int DEFAU ...

随机推荐

  1. Spring in Action 学习笔记一

    Spring 核心       Spring的主要特性仅仅是 依赖注入DI和面向切面编程AOP       JavaBean 1996.12 Javav 规范针对Java定义了软件组件模型,是简单的J ...

  2. ArcGIS Engine开发之地图基本操作(2)

    地图数据的加载 1.加载地图文档 ArcGIS Engine支持加载多种类型的数据,有矢量数据的Coverage.Shapefile.dwg/dxf文件,栅格数据的BMP.GRID.控件数据库等.很多 ...

  3. Android EventBus 3.0.0 使用总结

    转载请标明出处:http://www.cnblogs.com/zhaoyanjun/p/6039221.html 本文出自[赵彦军的博客] 前言 EventBus框架 EventBus是一个通用的叫法 ...

  4. React Native知识12-与原生交互

    一:原生传递参数给React Native 1:原生给React Native传参 原生给JS传数据,主要依靠属性. 通过initialProperties,这个RCTRootView的初始化函数的参 ...

  5. Android教程收集贴

    Loader & REST Rest Loader Tutorial [github源码] [源码演示] [github作者主页] Twitter Timeline Sample for An ...

  6. gitflow以及git

    git大概只会用常用的那几个命令,自己用的最多的也只是add commit push pull之类的,然后那天电话面试问我版本回退怎么办我都忘记了. 然后又看了看教程,此时想起来做项目的时候师兄说的g ...

  7. [Erlang 0121] 当我们谈论Erlang Maps时,我们谈论什么 Part 3

    Erlang/OTP 17.0 has been released  http://www.erlang.org/download/otp_src_17.0.readme     Erlang/OTP ...

  8. 原创 C++之常量(一)

    1概述 一个C++程序就是一系列数据与操作的集合.当一个C++程序开始运行的时候,与该程序相关的数据就会被加载到内存中.当数据与内存发生关联的时候,这些数据就会具有如下的特性: 数据在内存中的地址.这 ...

  9. favicon.ico 404

    favicon.ico是一个图标文件.就是浏览网站时显示在地址栏的那个图标. 类似是百度的 显示在网站地址最前面的一张图片 可以在网站根目录(TOMCAT_HOME/webapps/ROOT/favi ...

  10. Eclipse创建Maven工程报错

    问题 用Eclipse创建maven工程的时候,总是会报错,例如提示: Unable to create project from archetype [org.apache.maven.archet ...