一、存储结构

     在JDK1.8之前,HashMap采用桶+链表实现,本质就是采用数组+单向链表组合型的数据结构。它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap通过key的hashCode来计算hash值,不同的hash值就存在数组中不同的位置,当多个元素的hash值相同时(所谓hash冲突),就采用链表将它们串联起来(链表解决冲突),放置在该hash值所对应的数组位置上。结构图如下:

    图中,紫色部分代表哈希表,也称为哈希数组,数组中每个元素都是一个单链表的头结点,链表是用来解决冲突的,如果不同的key映射得到了数组的同一位置处,就将其放入单链表。

 

    在JDK1.8中,HashMap的存储结构已经发生变化,它采用数组+链表+红黑树这种组合型数据结构。当hash值发生冲突时,会采用链表或者红黑树解决冲突。当同一hash值的结点数小于8时,则采用链表,否则,采用红黑树。这个重大改变,主要是提高查询速度。它的结构图如下:

 

二、put方法

之所以先介绍存储结构,是为了更好的理解put方法。

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

put方法调用了putVal方法,那我们再来看看它。

/*
Parameters:
    hash hash for key
    key the key
    value the value to put
    onlyIfAbsent if true, don't change existing value
    evict if false, the table is in creation mode.
Returns:
    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;
    // 如果table为空,或者还没有元素时,则扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 如果首结点值为空,则创建一个新的首结点。
    // 注意:(n - 1) & hash才是真正的hash值,也就是存储在table位置的index。在1.6中是封装成indexFor函数。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {    // 到这儿了,就说明碰撞了,那么就要开始处理碰撞。
            Node<K,V> e; K k;
            // 如果在首结点与我们待插入的元素有相同的hash和key值,则先记录。
            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);
                            // 当遍历的结点数目大于8时,则采取树化结构。
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                                break;
                        }
                        // 如果找到与我们待插入的元素具有相同的hash和key值的结点,则停止遍历。此时e已经记录了该结点
                        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;
    // 当结点数+1大于threshold时,则进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict); // 这个是空函数,可以由用户根据需要覆盖
    return null;
}

 

参考:

1、JDK1.8HashMap原理和源码分析(java面试收藏)

2、Java类集框架之HashMap(JDK1.8)源码剖析

JDK1.8 HashMap中put源码分析的更多相关文章

  1. 关于JDK1.8 HashMap扩容部分源码分析

    今天回顾hashmap源码的时候发现一个很有意思的地方,那就是jdk1.8在hashmap扩容上面的优化. 首先大家可能都知道,1.8比1.7多出了一个红黑树化的操作,当然在扩容的时候也要对红黑树进行 ...

  2. 并发-HashMap和HashTable源码分析

    HashMap和HashTable源码分析 参考: https://blog.csdn.net/luanlouis/article/details/41576373 http://www.cnblog ...

  3. HashMap原理及源码分析

    HashMap 原理及源码分析 1. 存储结构 HashMap 内部是由 Node 类型的数组实现的.Node 包含着键值对,内部有四个字段,从 next 字段我们可以看出,Node 是一个链表.即数 ...

  4. 【原】Spark中Client源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...

  5. 【原】Spark中Master源码分析(二)

    继续上一篇的内容.上一篇的内容为: Spark中Master源码分析(一) http://www.cnblogs.com/yourarebest/p/5312965.html 4.receive方法, ...

  6. 【原】 Spark中Worker源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Worker源码分析(一)http://www.cnblogs.com/yourarebest/p/5300202.html 4.receive方法, r ...

  7. php中foreach源码分析(编译原理)

    php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...

  8. 手把手教你实现栈以及C#中Stack源码分析

    定义 栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作. 它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈), ...

  9. 基于JDK1.8,Java容器源码分析

    容器源码分析 如果没有特别说明,以下源码分析基于 JDK 1.8. 在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码. Lis ...

随机推荐

  1. Tomcat无法部署项目

    设置项目的Jdk,compire version 增加java EE 如果有必要,现在项目根目录下放置.mymetadata文件 <?xml version="1.0" en ...

  2. Android下得到已安装Application信息

    在上一篇blog中,谈到如何利用APK archive文件得到相应信息.(当时发现例如ProcessName,DataDir等信息,其实是无法得到的). 当前咱们看看如何通过系统取得已经安装的Appl ...

  3. [Bootstrap] 6. Navigation

    Too Much Navigation? How many of Bootstrap's Navigation Components can we use on a page? 0 1 More th ...

  4. (转) 如何在JavaScript与ActiveX之间传递数据1

    本文研究如何在JS等脚本语言与ActiveX控件之间通信,如何传递各种类型的参数,以及COM的IDispatch接口.使用类似的方法,可以推广到其他所有脚本型语言,如LUA,AutoCad等.本文将研 ...

  5. 面试题总结之Database

    SQL 1. 现有一张学生表,有只有一个列是名字,请选出其中的重名的学生的名字select name from student group by name having count(*) > 1 ...

  6. 学习笔记之SQL Programming DB2

    http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp If you intend to update a column or ...

  7. Apache rewrite 详解

    用rewrite可实现的部分:URL根目录搬迁,多目录查找资源,阻止盗连你的图片,拒绝某些主机访问,基于时间重写,据浏览器类型重写,动态镜像远程资源,外部重写程序模板,等等 详见下表: 目标 重写设置 ...

  8. nginx自定义模块编写-实时统计模块--转载

    原文:http://www.vimer.cn/2012/05/nginx%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9D%97%E7%BC%96%E5%86%99- ...

  9. Helpers Overview

    Helpers Overview Helpers are classes that are not part of the core system but can greatly improve it ...

  10. ios代理设计模式

    代理设计模式的作用:     1.A对象监听B对象的一些行为,A成为B的代理     2.B对象想告诉A对象一些事情,A成为B的代理   代理设计模式的总结:     如果你想监听别人的一些行为,那么 ...