首先,源码中上来就有一大段注释,但最重要的就是第一句。

大意如下:

  本map经常用作一个 binned (bucketed) hash table (下面有解释),但是,当bins很大的时候,它们会被转换成 bins of TreeNodes,每个bin的结构类似于TreeMap。

先解释下这里的bin,直译是容器、箱子。其实这里你可以认为它代表一个hash值对应的位置!

HashMap使用 HashMap.Node<K, V> 来存储具体的key和value,又用 Node[] 来存储Node。

transient Node<K,V>[] table;

要注意,这个Node实际上是链表结构的:

 static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //存储hash
final K key; //存储key
V value; //存储value
Node<K,V> next; //存储下一个Node,所以是链表结构 //其他略
}

如果不考虑hash碰撞,每当map.put(k, v)时,实际上是构建了一个Node,再将Node放到Node数组(也就是table)中。

当然位置是经过计算的:

table[(n-1) & hash] = newNode; //注意,不是数组的最后一个位置

但是,难免出现hash碰撞,也就是一个hash值有多个对象(或者多个对象的hash值一致)!这时候该怎么做?

其实在JDK7及之前,都是直接使用Node.next来安置的,也就是说,将新的对象追加到链表的尾部。

但是,JDK8有所改变,就是本文开头那句话讲述的内容。

直接看源码好了(对JDK的源码是恨得咬牙切齿,因为各种不人性化的东西):

 /**
* Implements Map.put and related methods
*
* @param hash k的哈希值
* @param key k
* @param value 要存储的v
* @param onlyIfAbsent true则不修改现有的值
* @param evict false 则 table是在creation mode。
* @return 前值,或null(如果没有前值)
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; //指向 HashMap的 table。transient Node<K,V>[] table;
Node<K,V> p; //用于指向 table 中的Node对象,如果该对象还有next,则最终会指向next。--当成prev比较合适,因为要插入的对象最终会放在它后面!
int n, i; //n是table的长度;i是table中的索引。
if ((tab = table) == null || (n = tab.length) == 0) //如果table为空(续)
n = (tab = resize()).length; //则重新分配 if ((p = tab[i = (n - 1) & hash]) == null) //(n-1)&hash 用于计算新对象的索引位置,如果该位置上的对象为null(续)
tab[i] = newNode(hash, key, value, null); //直接创建Node,放到该位置上!
else { //如果位置上已有对象(续)
Node<K,V> e;
K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //如果hash和key还一样(续)
e = p;
else if (p instanceof TreeNode) //如果hash和key不一样,就判断是否是TreeNode对象(续)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //是TreeNode对象的话,就调用putTreeVal(..)!
else { //到了这里,说明既不是同一个对象,也不是TreeNode,(续)
for (int binCount = 0; ; ++binCount) { //那就来个循环
if ((e = p.next) == null) { //如果当前Node没有next,
p.next = newNode(hash, key, value, null); //新建Node,并添加到当前Node.next
if (binCount >= TREEIFY_THRESHOLD - 1) //再判断链表的长度是否超出限制
treeifyBin(tab, hash);//超出指定长度,则将该链表转成树!
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //这里是判断当前链表中的Node存储的对象,与要存储的对象是否同一个
break;
p = e; //沿着链表前进一个位置。等同于p=p.next
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

源码注释不太方便啊,难道要来个流程图?还是算鸟,赶紧睡觉啦(*^_^*)

稍稍解读下JDK8的HashMap的更多相关文章

  1. 稍稍解读下ThreadPoolExecutor

    # 说说ThreadPoolExecutor ## 认识 先来看看它所在的架构体系: ```java package java.util.concurrent; public interface Ex ...

  2. JDK7与JDK8中HashMap的实现

    JDK7中的HashMap HashMap底层维护一个数组,数组中的每一项都是一个Entry transient Entry<K,V>[] table; 我们向 HashMap 中所放置的 ...

  3. 【JDK8】HashMap集合 源码阅读

    JDK8的HashMap数据结构上复杂了很多,因此读取效率得以大大提升,关于源码中红黑树的增删改查,博主没有细读,会在下一篇博文中使用Java实现红黑树的增删改查. 下面是类的结构图: 代码(摘抄自J ...

  4. 踩坑了,JDK8中HashMap依然会死循环!

    是否你听说过JDK8之后HashMap已经解决的扩容死循环的问题,虽然HashMap依然说线程不安全,但是不会造成服务器load飙升的问题. 然而事实并非如此.少年可曾了解一种红黑树成环的场景,=v= ...

  5. Windows 7 x64环境下JDK8安装过程

    Windows 7 x64环境下JDK8安装过程 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads ...

  6. Windows10下JDK8的下载安装与环境变量的配置

    Windows10下JDK8的下载安装与环境变量的配置 下载JDK8(64位) 链接:https://pan.baidu.com/s/10ZMK7NB68kPORZsPOhivog 提取码:agsa ...

  7. 深入分析 JDK8 中 HashMap 的原理、实现和优化

    HashMap 可以说是使用频率最高的处理键值映射的数据结构,它不保证插入顺序,允许插入 null 的键和值.本文采用 JDK8 中的源码,深入分析 HashMap 的原理.实现和优化.首发于微信公众 ...

  8. jdk7中hashmap实现原理和jdk8中hashmap的改进方法总结

    1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...

  9. weex官方demo weex-hackernews代码解读(下)

    weex 是阿里出品的一个类似RN的框架,可以使用前端技术来开发移动应用,实现一份代码支持H5,IOS和Android.而weex-hacknews则是weex官方出品的,首个使用 Weex 和 Vu ...

随机推荐

  1. CAD常用的快捷键命令

    CAD常用的快捷键命令大全,主要包括快捷键命令的键盘操作和所对应的快捷命令,方便查找和使用,对cad初学者能够快速提高绘图速度.图文并茂,非常实用. 一:常用功能键 F1: 获取帮助 F2: 实现作图 ...

  2. SpringMybatisMapper

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" ...

  3. 来自极客头条的 35 个 Java 代码性能优化总结

    前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用, ...

  4. 实战:mysql统计指定架构的全部表的数据和索引大小情况-v2

    PS:第一个版本号里未做输入的schema_name和table_name推断,改动了一下!再次share! #统计指定架构的全部表的数据和索引大小情况 #tablesize.sh #!/bin/sh ...

  5. iOS开发-获取设备型号信息

    开发中有的时候查看设计统计数据,或者通过日志查看错误信息,这个时候我们就需要获取获取设备信息,看下关于设备有几种方法: NSLog(@"%@",[[UIDevice current ...

  6. DNS的域名的解析解决办法(openDNS)

    http://www.williamlong.info/archives/1101.html

  7. he canvas has been tainted by cross-origin data and tainted canvases may not be exported

    来自: https://ourcodeworld.com/articles/read/182/the-canvas-has-been-tainted-by-cross-origin-data-and- ...

  8. eclipse Mars查看JDK源代码

    eclipse Mars查看JDK源代码 问题描写叙述,eclipse(mars)下看不到JDK类的声明即源代码部分的内容. 如图右击string类型: 点击打开声明.结果出现了下图所看到的的错误,无 ...

  9. Spark机器学习(4):朴素贝叶斯算法

    1. 贝叶斯定理 条件概率公式: 这个公式非常简单,就是计算在B发生的情况下,A发生的概率.但是很多时候,我们很容易知道P(A|B),需要计算的是P(B|A),这时就要用到贝叶斯定理: 2. 朴素贝叶 ...

  10. C++11 lambda表达式是如何实现的?

    lambda表达式是如何实现的呢? 其实是编译器为我们了创建了一个类,这个类重载了(),让我们可以像调用函数一样使用.所以,你写的lambda表达式和真正的实现,是这个样子的: 而对于捕获变量的lam ...