HashMap实现详解 基于JDK1.8

1.数据结构

散列表:是一种根据关键码值(Key value)而直接进行访问的数据结构。采用链地址法处理冲突。

HashMap采用Node<K,V>数组作为散列表来存储数据。源码声明如下:

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

Node<K,V>节点的源码如下,可见Node<K,V>有四个成员。

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final int hash;
  3. final K key;
  4. V value;
  5. Node<K,V> next;
  6. Node(int hash, K key, V value, Node<K,V> next) {
  7. this.hash = hash;
  8. this.key = key;
  9. this.value = value;
  10. this.next = next;
  11. }
  12. // 其余方法省略
  13. }

散列函数:HashMap的散列函数很简单,i = (n - 1) & hash,将hash值与Node<K,V>数组的大小n通过&运算即得到在Node<K,V>数组中的位置i。

2.关键变量

有几个关键的变量需要事先理解,源码定义如下:

  1. /**
  2. * The default initial capacity - MUST be a power of two.
  3. */
  4. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  5. // 这一步位移运算,得到结果16,作为Node<K,V>数组的初始大小,每次扩容都为原先的2倍。
  6. /**
  7. * The load factor used when none specified in constructor.
  8. */
  9. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  10. // 又称负载因子,定义了扩容的时机,默认为存储元素达到了16*75%时就要进行扩容。
  11. static final int TREEIFY_THRESHOLD = 8;
  12. // 因为采用链地址法处理冲突,当链表过长时,HashMap性能会下降,因此当链表的长度超过8时,会将链表转换为红黑树进行优化。
  13. static final int UNTREEIFY_THRESHOLD = 6;
  14. // 在哈希表扩容时,如果发现链表长度小于 6,则会由树重新退化为链表。
  15. static final int MIN_TREEIFY_CAPACITY = 64;
  16. // 只有存储数量大于 64 才会发生转换。这是为了避免在哈希表建立初期,多个键值对恰好被放入了同一个链表中而导致不必要的转化。

3.put方法

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  2. boolean evict) {
  3. Node<K,V>[] tab; Node<K,V> p; int n, i;
  4. // 判断散列表是否为空,若是,则进行初始化
  5. if ((tab = table) == null || (n = tab.length) == 0)
  6. n = (tab = resize()).length;
  7. // 判断散列表位置是否冲突,若否,直接存储
  8. if ((p = tab[i = (n - 1) & hash]) == null)
  9. tab[i] = newNode(hash, key, value, null);
  10. // 处理冲突
  11. else {
  12. Node<K,V> e; K k;
  13. if (p.hash == hash &&
  14. ((k = p.key) == key || (key != null && key.equals(k))))
  15. e = p;
  16. else if (p instanceof TreeNode)
  17. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  18. else {
  19. for (int binCount = 0; ; ++binCount) {
  20. if ((e = p.next) == null) {
  21. p.next = newNode(hash, key, value, null);
  22. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  23. treeifyBin(tab, hash);
  24. break;
  25. }
  26. if (e.hash == hash &&
  27. ((k = e.key) == key || (key != null && key.equals(k))))
  28. break;
  29. p = e;
  30. }
  31. }
  32. if (e != null) { // existing mapping for key
  33. V oldValue = e.value;
  34. if (!onlyIfAbsent || oldValue == null)
  35. e.value = value;
  36. afterNodeAccess(e);
  37. return oldValue;
  38. }
  39. }
  40. ++modCount;
  41. if (++size > threshold)
  42. resize();
  43. afterNodeInsertion(evict);
  44. return null;
  45. }

put流程图

put流程总结:

1.根据Key计算hash,得到散列地址

2.若散列表大小为0,则初始化大小为16

3.若散列地址无冲突,则直接存储。若有冲突,则将元素存入链表或红黑树

4.当链表长度大于8,且总元素个数大于64时,将链表转换为红黑树

5.当总元素个数达到Capacity的75%时,将散列表扩容为2倍

4.get方法

  1. final Node<K,V> getNode(int hash, Object key) {
  2. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  3. if ((tab = table) != null && (n = tab.length) > 0 &&
  4. (first = tab[(n - 1) & hash]) != null) {
  5. if (first.hash == hash && // always check first node
  6. ((k = first.key) == key || (key != null && key.equals(k))))
  7. return first;
  8. if ((e = first.next) != null) {
  9. if (first instanceof TreeNode)
  10. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  11. do {
  12. if (e.hash == hash &&
  13. ((k = e.key) == key || (key != null && key.equals(k))))
  14. return e;
  15. } while ((e = e.next) != null);
  16. }
  17. }
  18. return null;
  19. }

get流程比较简单,直接总结一下:

1.根据Key计算hash,然后根据hash计算散列地址

2.判断元素hash与Key是否同时相等,相等则返回

3.若不相等,节点属于红黑树节点则在红黑树中查找,不属于则遍历链表查找

4.没有此节点则返回null

5.hash方法

  1. /**
  2. * Computes key.hashCode() and spreads (XORs) higher bits of hash
  3. * to lower. Because the table uses power-of-two masking, sets of
  4. * hashes that vary only in bits above the current mask will
  5. * always collide. (Among known examples are sets of Float keys
  6. * holding consecutive whole numbers in small tables.) So we
  7. * apply a transform that spreads the impact of higher bits
  8. * downward. There is a tradeoff between speed, utility, and
  9. * quality of bit-spreading. Because many common sets of hashes
  10. * are already reasonably distributed (so don't benefit from
  11. * spreading), and because we use trees to handle large sets of
  12. * collisions in bins, we just XOR some shifted bits in the
  13. * cheapest possible way to reduce systematic lossage, as well as
  14. * to incorporate impact of the highest bits that would otherwise
  15. * never be used in index calculations because of table bounds.
  16. */
  17. static final int hash(Object key) {
  18. int h;
  19. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  20. }

这是HashMap中计算hash值的方法,可以看出,这里首先调用key.hashCode()得到key的hash,然后将hash右移16位(最高为补0),与自身进行^(异或)运算,得到最终hash。这里有一个疑问,为什么要将hash值右移16位再来一个异或呢,直接用初始值不行吗?下面来看解释:

我们知道,HashMap中的散列地址是根据hash值得到的,方法是 (capacity-1)&hash,这个方法虽然很快,但是也带来一些副作用,这里也解释了为什么HashMap每次扩容都为2倍,因为这样 (capacity-1)恰好是一个掩码


  1. 10110001 10100101 | 11000100 00100101 // hash值
  2. & 00000000 00000000 | 00000000 00001111 // 16-1, 假设capacity为初始值16
  3. -----------------------------------------
  4. 00000000 00000000 | 00000000 00000101 // 最终值5

当计算散列地址时,直接舍弃了高位的信息,只使用了capacity大小的容量,因此很有可能造成大量的地址冲突,效率降低。因此要利用被舍弃的高位信息,一个办法就是将高位与地位做^(异或)运算,使低位参杂高位信息。


  1. 10110001 10101001 | 11000100 00100101 // h=key.hashCode()
  2. ^ 00000000 00000000 | 10110001 10101001 // h >>> 16
  3. -----------------------------------------
  4. 10110001 10101001 | 01110101 10001100 // hash

右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

总结:hash()函数的作用是为了减少散列函数的副作用。

HashMap实现详解 基于JDK1.8的更多相关文章

  1. HashMap详解 基于jdk1.7

    转载自:http://zhangshixi.iteye.com/blog/672697 1.    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操 ...

  2. 转:Java HashMap实现详解

    Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1.    HashMap概述:    HashMap是基于哈希表的M ...

  3. JS hashMap实例详解

    链接:http://www.jb51.net/article/85111.htm JS hashMap实例详解 作者:囧侠 字体:[增加 减小] 类型:转载 时间:2016-05-26我要评论 这篇文 ...

  4. HashMap 源码分析 基于jdk1.8分析

    HashMap 源码分析  基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table;  //这里维护了一个 Node的数组结构: 下面看看Node的数 ...

  5. nrf52——DFU升级USB/UART升级方式详解(基于SDK开发例程)

    摘要:在前面的nrf52--DFU升级OTA升级方式详解(基于SDK开发例程)一文中我测试了基于蓝牙的OTA,本文将开始基于UART和USB(USB_CDC_)进行升级测试. 整体升级流程: 整个过程 ...

  6. MapReduce过程详解(基于hadoop2.x架构)

    本文基于hadoop2.x架构详细描述了mapreduce的执行过程,包括partition,combiner,shuffle等组件以及yarn平台与mapreduce编程模型的关系. mapredu ...

  7. 详解基于linux环境MySQL搭建与卸载

    本篇文章将从实际操作的层面,讲解基于linux环境的mysql的搭建和卸载. 1  搭建mysql 1.1  官网下载mysql压缩包 下载压缩包时,可以先把安装包下载到本地,再上传到服务器,也可以在 ...

  8. 【Java基础】HashMap原理详解

    哈希表(hash table) 也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中Has ...

  9. nrf52——DFU升级OTA升级方式详解(基于SDK开发例程)

    在我们开始前,默认你已经安装好了一些基础工具,如nrfutil,如果你没有安装过请根据官方中文博客去安装好这些基础工具,连接如下:Nordic nRF5 SDK开发环境搭建(nRF51/nRF52芯片 ...

随机推荐

  1. MSXM简单的使用

    // xml.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <string> #include <at ...

  2. $51nod\ 1522$ 上下序列 $dp$

    正解:$dp$ 解题报告: 传送门$QwQ$ 一年过去了$gql$还是不咋会这题,,,好菜昂我的$NOIp$必将惨败了$kk$ 考虑从大到小枚举两个相同的数填哪儿,根据那个限制,十分显然的是这两个数必 ...

  3. 洛谷$P$3293 美味 $[SCOI2016]$ 主席树

    正解:主席树 解题报告: 传送门! 挺有趣的,至少我不会$QAQ$(虽然我不会的多了去了$QAQ$ 如果没有这个所谓美味度限制可以直接线段树水过去嘛$QwQ$ 然后现在问的是个异或运算后的结果,关于异 ...

  4. JVM探秘:垃圾收集算法

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 垃圾收集算法 垃圾收集算法主要有标记-清除算法.复制算法.标记-整理算法.分代收集算法 ...

  5. Arrays.asList() 导致的java.lang.UnsupportedOperationException异常

    Arrays.asList() 只支持遍历和取值 不支持增删改 继承至AbstractList内部类

  6. VS2019菜单栏没有团队选项解决方法

    新安装了Visual Studio 2019结果菜单栏没有“团队”菜单,导致没办法连接TFS服务器,查了下网上也并没有对应解决方法(甚至遇见这个问题的都没有/笑哭,所以这个方法写出来也大概没什么用) ...

  7. 最新IDEA永久激活攻略

    前言 写这篇文章的原因是我最近想自己写两个项目,却发现自己的IDEA过期了,对,就是那个JAVA编辑器,于是研究了一下IDEA的激活.发现网上的攻略大多数不可用. 当然这里推荐大家去官网购买正版使用. ...

  8. STM32F429的特点

    STM32F429 内核 Crotex M4 最高主频 180MHZ FPU 有 DSP指令集 有 最大SRAM 256K 备份域SRAM 有 最大FLASH 2MB GPIO最高翻转速度 90MHZ ...

  9. poj 2689 区间素数筛

    The branch of mathematics called number theory is about properties of numbers. One of the areas that ...

  10. 获取当前URL

    HttpContext.Current.Request.Url.ToString();