一、存储结构

     在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. Uploadify 3.2 参数属性、事件、方法函数详解

    一.属性 属性名称 默认值 说明 auto true 设置为true当选择文件后就直接上传了,为false需要点击上传按钮才上传 . buttonClass ” 按钮样式 buttonCursor ‘ ...

  2. MySQL主从复制技术(纯干货)

    1.复制配置     主机一定要开启二进制日志(这里建议配置RBR)     每个主机和每个从机一定要配置一个位移的id,即server-id     每个从机配置一定要包含主机名称,日志名称,和位置 ...

  3. 检查class排座位

    在写这篇文章之前,xxx已经写过了几篇关于改检查class主题的文章,想要了解的朋友可以去翻一下之前的文章     每日一道理 灯,带有一种明亮的光,每当深夜来临,是它陪伴着你,如此默默无闻.它是平凡 ...

  4. cdll和windll的差别

    Python要想调用C语言写的动态连接库.不仅要兼容C接口的调用习惯,还须要兼容C语言的数据类型.幸运的是ctypes库已经做了这双方面的工作.以便调用动态连接库是很方便的.在Hello World的 ...

  5. hdu4759 Poker Shuffle 2013 ACM/ICPC Asia Regional Changchun Online

    找了很久的规律,只看十进制数字,各种乱七八糟的规律=没规律!看了别人的解题报告,虽然看懂了,可是怎么发现的这个规律呢T.T~想了很久很久~ 以下是转载的别人的图,自己再画太麻烦了~全部看出0~2n-1 ...

  6. c++ 怎样获取系统时间

    c++ 怎样获取系统时间 2008-04-28 15:34 //方案— 长处:仅使用C标准库:缺点:仅仅能精确到秒级 #include <time.h> #include <stdi ...

  7. interactive_timeout和wait_timeout(

    mysql> show variables like "%timeout%"; +-----------------------------+----------+ | Va ...

  8. forward_list例子

    9.28 编写函数,接受一个forward_list<string>和两个string共三个参数.函数应在链表中查找第一个string,并将第二个string插入到紧接着第一个string ...

  9. [Effective C++ --008]别让异常逃离析构函数

    这章非常容易理解:因为C++并不禁止析构函数吐出异常,只是不鼓励这样做而已. 一.原因 假设我们有10个装着鸡蛋的容器,而且现在我们还想着把它在析构函数打烂. class Egg { public : ...

  10. ERROR:_OBJC_CLASS_$_ADBannerView

    http://stackoverflow.com/questions/4127489/iads-integration-with-cocos2d You are getting that error ...