Java HashMap

jdk 1.8

Java8相对于java7来说HashMap变化比较大,在hash冲突严重的时候java7会退化为链表,Java8会退化为TreeMap

我们先来看一下类图:

可见,HashMap继承了AbstractMap,但是Map并没有扩展Collection接口

我们先来看一下put方法

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

K,V分别是键值,首先对key执行hash方法,

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

首先取key的hashCode然后和hashcode右移16位(高位用0补全)然后进行异或

不用hashCode直接接返回的原因在这,我在下面的注释也稍微解释了一下

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//首先把table赋值给tab 检查是不是null 如果是null 那么初始化一下(resize)
//饭后返回table的长度给n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(n-1)&hash作为桶的标号
//我们知道桶的数量是 2^n
//2^n - 1 在二进制中表示就是全是1 按位与就比较平均的分配桶了
//但是为什么不直接用hashcode作为hash呢?
//由于使用桶数量-1作为掩码,那么注定高位的hashcode无法参与运算
//我们把高16位与低16使用xor运算,让高位参与hash,减少一定的冲突
//但是如果 桶的数量不是 2^n 那么这么做hash就不那么平均了
//在这里判断是不是有节点,如果没有,那么证明这里没有Hash冲突直接创建一个节点放这里就好了
if ((p = tab[i = (n - 1) & hash]) == null){
//在这里创建一个节点赋值给第i个桶
tab[i] = newNode(hash, key, value, null);
}
else {
//这里证明有Hash冲突了
Node<K,V> e; K k;
//判断当前的hash是不是相等,然后判断key相等
//因为key是个对象,相对来讲equals运算时间更长,先比较hash,如果不相等就不用equals比较了
//如果是相同的键,那么赋值给一个临时引用,先不覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//这里判断当前桶里的头结点是不是TreeNode,如果是TreeNode,那就把这个节点插到红黑树里面
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果不是Tree节点,那么说明现在还是个链表
//开始遍历
for (int binCount = 0; ; ++binCount) {
//判断是不是尾节点
if ((e = p.next) == null) {
//是尾节点那就直接插后面
p.next = newNode(hash, key, value, null);
//判断链表多长
//如果超过阈值,那就把链表变成二叉树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//hash相等之后在进行equals判断如果相等,证明这个已经key已经存在了break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//e是key所在的节点如果是null那么证明是新插的,否则覆盖引用
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//如果旧值是null或onlyIfAbsent==0那么覆盖值然后直接返回就可以了
//因为并没有改变table结构或者链表什么的
e.value = value;
//这个是空的,需要子类覆盖一下
//但是绝不能改变table结构什么的
afterNodeAccess(e);
return oldValue;
}
}
//更改迭代器
++modCount;
//大于阈值 扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

先看一下注释

第一个值是key 哈希之后的值,第二个是key 第三个 是value 第四个就是是否覆盖原来存在的值,第五个是当前表是不是正在处于创建状态(false表示不是create)

看一下resize 操作

实际上就是扩容,解释写在注释里面了

    /**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//如果是null那么说明是新创建的
//下面的几个变量,Cap桶容量,Thr阈值
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//先判断旧桶有没有
if (oldCap > 0) {
//如果旧的长度大于最大阈值,那么把桶扩大到最大Integer.MAX,然后不会发生resize了
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//桶容量*2如果比最大容量小以及旧的大于默认桶容量
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//旧的没桶数量 但是有阈值 说明第一次创建
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//没有阈值,没有旧桶容量,说明默认初始化的
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
//新阈值 为0 说明需要计算阈值
float ft = (float)newCap * loadFactor;
//cap*factor但是如果太大,那么就扩大到Integer.MAX_VALUE
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)
newTab[e.hash & (newCap - 1)] = 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;
//这里挺有意思的
//判断 oldCap那个位置是0还是1
//如果是0 那么扩容后不变
//如果是1 那么扩容后桶是原来位置+原来桶的数量
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;
}

红黑树那部分先不看了

可以参考一下这个,里面有图很好理解:

https://www.cnblogs.com/mzc-blogs/p/5800084.html

Java HashMap 源代码分析的更多相关文章

  1. Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...

  2. java TreeMap 源代码分析 平衡二叉树

    TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...

  3. Java ThreadLocal 源代码分析

    Java ThreadLocal 之前在写SSM项目的时候使用过一个叫PageHelper的插件 可以自动完成分页而不用手动写SQL limit 用起来大概是这样的 最开始的时候觉得很困惑,因为直接使 ...

  4. Java ConcurrentHashMap 源代码分析

    Java ConcurrentHashMap jdk1.8 之前用到过这个,但是一直不清楚原理,今天抽空看了一下代码 但是由于我一直在使用java8,试了半天,暂时还没复现过put死循环的bug 查了 ...

  5. Java ArrayList 源代码分析

    Java ArrayList 之前曾经参考 数据结构与算法这本书写过ArrayList的demo,本来以为实现起来都差不多,今天抽空看了下jdk中的ArrayList的实现,差距还是很大啊 首先看一下 ...

  6. Android应用程序进程启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...

  7. Java集合源代码剖析(二)【HashMap、Hashtable】

    HashMap源代码剖析 ; // 最大容量(必须是2的幂且小于2的30次方.传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << ...

  8. Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

  9. 【Java集合源代码剖析】HashMap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 您好,我正在參加CSDN博文大赛,假设您喜欢我的文章.希望您能帮我投一票.谢 ...

随机推荐

  1. iOS设计模式 - 桥接

    iOS设计模式 - 桥接 示意图 说明 1. 桥接模式为把抽象层次结构从实现中分离出来,使其可以独立变更,抽象层定义了供客户端使用的上层抽象接口,实现层次结构定义了供抽象层次使用的底层接口,实现类的引 ...

  2. SCOM中的通配符

    通配符模式匹配按从左到右的方式完成,一次匹配一个字符或基本通配符模式.模式和传入字符串必须完全匹配,因此,举例来说,模式“abc”与字符串“abcd”不匹配.复合模式包含由 (&) 号或波形符 ...

  3. yum 安装 lnmp

    一. 系统 更新 yum -y update 二. 安装nginx 创建文件 vim /etc/yum.repos.d/nginx.repo 文件内容,这配置是安装最新的稳定版1.8 [nginx] ...

  4. Github文件夹下载到本地

    1.如图:需要将以下文件夹下载到本地. https://github.com/aspnet/Docs/tree/master/aspnet/mvc/overview/getting-started/i ...

  5. 【转】iphone 输入/输出流异步读写数据

    原文:iphone 输入/输出流异步读写数据 分类: iphone2012-05-30 14:50 2484人阅读 评论(1) 收藏 举报 iphoneattributesinterfacepaths ...

  6. 关于crontab中的一些小问题

    今天写了个脚本,要写进crontab做定时任务,每5分钟跑一遍.关于crontab的介绍呢,请大家移步www.baidu.com,在这里我就不仔细介绍了.可以搜索一下“每天一个Linux命令”系列文章 ...

  7. js oc与线程

    分属不同的线程 //定义需要暴露给js的内容,这里我们只暴露personName和queryPersonName接口 @protocol PersonProtocol <JSExport> ...

  8. win10中显示资源管理器扩展

    一年前从有两台机器win7升级到win10,一台上装了我常用的资源管理器扩展setExBar,但另一台没有.升级后原来有插件的依然默认显示插件,我在另一台机器上安装了setExBar时默认不显示.如果 ...

  9. 【jQuery】cookie插件

    通过该插件的学习使我对cookie.Date().getDate().setDate().toUTCString()有了更直观的了解,具体分析见注释: function(key, value, opt ...

  10. XCODE7 和IOS9适配后的一些问题

    网上比较常规的几个问题就不细说了. 什么HTTPS.bitcode.什么什么的. 记录几个自己又遇到了但是网上没有说的. 启动应用报错, *** Terminating app due to unca ...