HashMap原理-1.8-程序员宅基地

技术标签: java  数据结构与算法  

之前总结过HashMap的原理,同时对源码进行了阅读,不过是针对JDK1.7的版本,同样针对1.8的版本也来做一遍记录

1.HashMap1.8针对1.7的修改点有哪些?

JJDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也可以使用红黑树进行存储,总之,目标只有一个,那就是在安全和功能性完备的情况下让其速度更快,提升性能。

具体结构大致如下,该图从别处借来

 

接下来还是从源码的角度的来分析1.8的HashMap

1.数据结构

/**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;  //定义数组

/**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K,V> implements Map.Entry<K,V> { //内部类,定义Node
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

2.put方法


public V put(K key, V value) {
       
//对key对hash获取Hash值
return putVal(hash(key), key, value, false, true);
}

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) //如果table为空,则进行扩容处理 n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) //计算index,对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 //判断链表数是否超过8,如果超过,则转换为红黑树 treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) //如果key存在则覆盖 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; }

再借个流程图说明:

 

3.get方法 

Get方法还是类似,获取hashindex,再判断第一个节点是否为红黑树,如果是红黑树,则通过树的方式获取,否则还是按照链表的方式获取 

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;
    }

 

4.扩容

之前讲到1.8对1.7的扩容处理进行了优化,优化在哪个方面?

1.7的扩容,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话),进行扩容的时候需要重新计算hash index

1.8的扩容,使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置;

因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。

 

posted on 2017-07-14 11:05  qiezijiajia 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/dpains/p/7169030.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/angweichun6648/article/details/101810034

智能推荐

【Android】实现键盘收起的时候,输入框UI也消失_安卓收起键盘-程序员宅基地

文章浏览阅读498次。之前对软键盘操作,实现对点击输入框出现软键盘(即手机默认的键盘)现在有一个需求是:软键盘收起的时候,咱的输入框UI也消失。_安卓收起键盘

基于Python实现的电影数据可视化分析系统附完整代码_影视网站的数据可视化分析-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏46次。基于Python实现的电影数据可视化分析系统附完整代码_影视网站的数据可视化分析

Qt入门:3 Qt界面布局管理详解_qt vertical line 移动-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏8次。实例讲解ln_2双击dialog.ui进入设计界面,进行如下设计:程序的主要功能是对中间一个文本框的文字字体样式和颜色进行设置。在界面设计时,对需要访问的组件修改其 objectName,如各个按钮、需要读取输入的编辑框、需要显示结果的标签等,以便在程序里区分。对于不需要程序访问的组件则无需修改其 objectName,如用于界面上组件分组的 Gr..._qt vertical line 移动

CRC冗余校验码及查表法_crc4错误概率-程序员宅基地

文章浏览阅读987次。CRC冗余校验码及查表法什么是CRC编码它将一个长度为k的位串看作是系数是0或者1的k-1次多项式使用一个长度为r+1的生成多项式进行模2计算,生成一个长度为r的字符序列,能检测长度小于等于r的所有突发错误,当突发错误长度为r+1时,只有其刚刚好等于生成多项式,才检测不出来。多项式的最高位、最低位系数必须为1(我不知道为什么)计算方法:(此处使用的减法是模2减法,不进位不借位,相当于XOR运算)例如:使用G(x)=11001检测位串1011011010110110000011001----_crc4错误概率

STM32+ESP8266+DHT11通过MQTT协议连接新版ONENET云平台上传数据_新版onenet连接stm32-程序员宅基地

文章浏览阅读5.7k次,点赞9次,收藏119次。前段时间ONENET云平台进行了升级更新,此前平台的多协议接入(包含旧版MQTT、HTTP、EDP、Modbus、TCP透传等)接口已经隐藏,后续应该会下架,为了能够后续继续使用ONENET云平台,就需要学会使用将数据上传到新版ONENET云平台。经过一段时间的摸索,现在可以成功将数据上传。此次使用MQTT协议将温湿度通过ESP8266_WIFI模块上传到新版ONENET云平台,并使用app.wxbit.com图形化APP制作工具制作APP调用ONENET云平台提供的API接口实时显示温湿度数据。_新版onenet连接stm32

百度网盘直接解析高速下载文件源码_百度网盘解析-程序员宅基地

文章浏览阅读5.6k次。介绍:百度网盘直接高速下载文件源码上传源码 访问域名跳转安装页面填写相关信息 安装完成源码功能:通过curl获取网盘文件信息,处理后显示在网页中。通过api接口以及SVIP账号的Cookie(BDUSS)获取高速下载链接。网盘下载地址:http://kekewl.cc/yk09TgCFisr0图片:..._百度网盘解析

随便推点

红帽oracle关系,redhat和oracle linux kernel对应关系-程序员宅基地

文章浏览阅读1.4k次。Red Hat Enterprise Linux Version / UpdateRed Hat Enterprise Linux – Kernel version / redhat-release stringOracle Linux – Kernel version / release stringsRed Hat Enterprise Linux 7Red Hat Enterprise Li..._oracle linux redhat 对应关系

2020年中南大学研究生招生夏令营机试题_中南大学 计算机 夏令营 笔试-程序员宅基地

文章浏览阅读804次。2020年中南大学研究生招生夏令营机试题题目链接A题题目描述众所周知,彩虹有7种颜色,我们给定七个 字母和颜色 的映射,如下所示:‘A’ -> “red”‘B’ -> “orange”‘C’ -> “yellow”‘D’ -> “green”‘E’ -> “cyan”‘F’ -> “blue”‘G’ -> “purple”但是在某一..._中南大学 计算机 夏令营 笔试

Cmake的option与cmake_dependent_option-程序员宅基地

文章浏览阅读2.9k次。一、介绍cmake提供了一组内置宏,用户可以自己设置。只有当该集合中的其他条件为真时,该宏才会向用户提供一个选项。语法include(CMakeDependentOption)CMAKE_DEPENDENT_OPTION(USE_FOO "Use Foo" ON "USE_BAR;NOT USE_ZOT" OFF)如果USE_BAR为true而USE_ZOT为false,则提供一个默认为ON的选项USE_FOO。否则,它将USE_FOO设._cmake_dependent_option

C++ =default-程序员宅基地

文章浏览阅读5.2k次,点赞10次,收藏34次。在c++中如果我们自行定义了一个构造函数,那么编译器就不会再次生成默认构造函数,我们先看如下的代码我们定义一个类,这个类没有定义构造函数,此时在下面一段代码依然可以正常使用,我们加上一个自定义构造函数:此时编译器会报错,原因很简单,我们自定义了一个构造函数,以前的默认构造函数没了,我们要用如下的方式调用:如果我们还要使用无参构造函数得在定义时自己写个好了此时不报错了,但是这样写代码执行效率没有编译器生成的自定义函数的效率高,为了解决这个问题,C++11 标准引入了一个新特性:default 函数。程_c++ =default

linux gcc 4.8.2,gcc4.8.2 编译异常-程序员宅基地

文章浏览阅读446次。gcc4.8.2 编译错误 求助checkingfor--enable-version-specific-runtime-libs...nocheckingfor--enable-generated-files-in-srcdir...nocheckingbuildsystemtype...i686-pc-linux-gnucheckinghostsystemtype....._gcc libatomic error

IOS企业版app部署到自己服务器,不通过AppStore,在iOS设备上直接安装应用程序_plist中内嵌的下载地址 带参-程序员宅基地

文章浏览阅读2.7w次。IOS企业版app部署到服务器上说明 正对ios升级得ios7 以后,plist文件必须放到 https得服务器上了,http不可以用了。 解决方式: 找一个第三方https外链的网盘(推荐:七牛云存储https://portal.qiniu.com/),将plist文件放到网盘上,ipa安装包可以放在 自己的服务器上。不通过在AppStore,在IOS设备上直接安_plist中内嵌的下载地址 带参