兴趣所致研究一下HashMap的源码,写下自己的理解,基于JDK1.8。

本文大概分析HashMap的put(),get(),resize()三个方法。

首先让我们来看看put()方法。

    public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
    /**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
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;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
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;
}

1.首先通过hash(key)计算key的hash值

putVal(hash(key), key, value, false, true)

2.由于hashMap实际存储数据的就是table数组,那么首先需要判断table数组是否已经被初始化了,如果没有初始化,那么调用resize()方法对table进行初始化

if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;

3.通过hash与(n-1)进行与运算得出数组的索引,根table[索引]判断是否为null,如果为null那么直接存入该位置。

if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);

4.如果table[索引]不为null,那么说明位置发生了碰撞,需要继续进行判断

5.如果判断table[索引]位置p上的p.key=key&&p.hash=hash,那么就会对value进行替换,也就是保证key唯一。

6.如果不满足5的条件,那么会判断table[索引]位置p如果是二叉树节点,那么会调用TreeNode.putTreeVal()去进行对二叉树节点的插入。

else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

7.如果不满足5,6的条件,那么会使用链表来插入该节点:循环寻找p.next直到找到一个位置为null那么进行插入。

                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;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}

8.上述5,6,7三步中,只要发现了p.key=key&&p.hash=hash,那么就会进行value替换,将oldValue替换为newValue。

            if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}

9.进行计数+1,并且判断当前已存数量是否大于table[]的2/3,如果大于那么使用resize()对table进行扩充。

10.至此整个put()完成。

再来让我们看看get()方法。

    public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
    /**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}

1.首先判断table[]是否为空,通过hash计算索引判断table[索引]是否为空,如果任意一项为空那么直接return null。

2.判断table[索引]的hash与key是否都与查找的相同,如果hash与key都相同,那么直接返回table[索引]。

if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;

3.如果不满足条件2,那么判断table[索引]的next是否为空,如果为空则return null。

4.如果table[索引]的next不为空,那么判断是否为二叉树,如果是二叉树直接使用getTreeNode()方法查找。

if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);

5.如果不是二叉树,那么直接使用链表的方式查找。

        do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);

6.至此完成整个get()方法。

Java --HashMap源码解析的更多相关文章

  1. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  2. Java HashMap 源码解析

    今天正式开始分析具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 签名(signature) public class HashMap<K,V> extends Abstract ...

  3. Java——HashMap源码解析

    以下针对JDK 1.8版本中的HashMap进行分析. 概述     哈希表基于Map接口的实现.此实现提供了所有可选的映射操作,并且允许键为null,值也为null.HashMap 除了不支持同步操 ...

  4. Java——LinkedHashMap源码解析

    以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述   哈希表和链表基于Map接口的实现,其具有可预测的迭 ...

  5. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

  6. HashMap源码解析 非原创

    Stack过时的类,使用Deque重新实现. HashCode和equals的关系 HashCode为hash码,用于散列数组中的存储时HashMap进行散列映射. equals方法适用于比较两个对象 ...

  7. Java HashMap源码详解

    Java数据结构-HashMap 目录 Java数据结构-HashMap 1. HashMap 1.1 HashMap介绍 1.1.1 HashMap介绍 1.1.2 HashMap继承图 1.2 H ...

  8. 自学Java HashMap源码

    自学Java HashMap源码 参考:http://zhangshixi.iteye.com/blog/672697 HashMap概述 HashMap是基于哈希表的Map接口的非同步实现.此实现提 ...

  9. Java集合类源码解析:Vector

    [学习笔记]转载 Java集合类源码解析:Vector   引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...

随机推荐

  1. 好友录v1.2.7_Build(7790)

    <好友录>是使用.net Framework4.0+sqlite开发的,属于WingKu(谷毅科技)系列软件之一.它是一款外观时尚.美观,操作简单.易用,功能强大的个人通讯信息管理软件.它 ...

  2. react3 组件

    <body><!-- React 真实 DOM 将会插入到这里 --><div id="example"></div> <!- ...

  3. CCNA学习 NAT网络地址转换

    CCNA基础 NAT网络地址转换 在计算机网络中,网络地址转换(Network Address Translation,缩写为NAT),也叫做网络掩蔽或者IP掩蔽(IP masquerading),是 ...

  4. WPF自定义控件与样式(9)-树控件TreeView与菜单Menu-ContextMenu

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 菜单M ...

  5. 【转】“正由另一进程使用,因此该进程无法访问该文件”的问题&解决方法

    正在写一个手指画图的程序C# + WPF其中有一部分是加载外部某PNG文件,放入BitmapImage,再作为Image的Source显示在Canvas上画了几笔之后,再存回这个PNG文件 ===== ...

  6. JavaScript之毒瘤

    0.导言 JavaScript中有许多难以避免的问题特性.接下来就一一揭示. 1.全局变量 在所有JavaScript的糟糕特性中,最为糟糕的就是全局变量的依赖.全局变量使得在同一个程序中运行独立的子 ...

  7. 邻接表无向图(三)之 Java详解

    前面分别介绍了邻接表无向图的C和C++实现,本文通过Java实现邻接表无向图. 目录 1. 邻接表无向图的介绍 2. 邻接表无向图的代码说明 3. 邻接表无向图的完整源码 转载请注明出处:http:/ ...

  8. Docker的学习--命令使用详解

    使用命令查看一下docker都有那些命令: docker -h 你将得到如下结果: A self-sufficient runtime for linux containers. Options: - ...

  9. Javascript定时器(三)——setTimeout(func, 0)

    setTimeout(func, 0)可以使用在很多地方,拆分循环.模拟事件捕获.页面渲染等 一.setTimeout中的delay参数为0,并不是指马上执行 <script type=&quo ...

  10. Sql Server优化之索引提示----我们为什么需要查询提示,Sql Server默认情况下优化策略选择的不足

    环境: Sql Server2012 SP3企业版,Windows Server2008 标准版 问题由来: 最近在做DB优化的时候,发现一个存储过程有非常严重的性能问题, 由于整个SP整体逻辑是一个 ...