JDK版本 1.8

结构:

HashMap实现了Map Cloneable Serializable接口;

基础了AbstractMap类,AbstractMap提供一些通用方法,如put remove等,但子类一般会重写put等方法;

  1. public class HashMap<K,V> extends AbstractMap<K,V>
  2. implements Map<K,V>, Cloneable, Serializable{
  3. }

HashMap中定义了一些常量

  1. DEFAULT_INITIAL_CAPACITY 1 << 4 默认初始化容量值
  2. MAXIMUM_CAPACITY 1 << 30 最大容量值
  3. DEFAULT_LOAD_FACTOR 0.75f 默认负载因子
  4. TREEIFY_THRESHOLD 8 树化门槛值
  5. UNTREEIFY_THRESHOLD 6 反树化门槛值
  6. MIN_TREEIFY_CAPACITY 64 最小树化容量

数据结构节点

HashMap中定义了2个重要的数据结构,Node和TreeNode

  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. // hashCode值是key和value的hashcode取异或值
  7. public final int hashCode() {
  8. return Objects.hashCode(key) ^ Objects.hashCode(value);
  9. }
  10. }

存储节点Node的容器

  1. // 实际上就是一个Node数组
  2. transient Node<K,V>[] table;

hash方法

相当于是HashMap自己的取Hash值的方法

  1. // 将key的hashcode值和hashcode值的低16值进行异或 记过就是在HashMap中的hash值
  2. static final int hash(Object key) {
  3. int h;
  4. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  5. }

HashMap的put方法实现

put函数大致的思路为:

  1. 对key的hashCode()做hash,然后再计算index;
  2. 如果没碰撞直接放到bucket里;(实际上是看index所在的位置是否有节点)
  3. 如果碰撞了,以链表的形式存在buckets后;
  4. 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
  5. 如果节点已经存在就替换old value(保证key的唯一性)
  6. 如果bucket满了(超过load factor*current capacity),就要resize。

put()方法是public的,会调用putVal()方法。

  1. 计算index,通过i = (n - 1) & hash来确定桶的位置 再根据桶是否为空(以及桶元素的长度 延长链表或者变树)

代码

  1. // 先对key进行hash计算 再调用putVal去存
  2. public V put(K key, V value) {
  3. return putVal(hash(key), key, value, false, true);
  4. }
  5. /**
  6. * Implements Map.put and related methods
  7. *
  8. * @param hash hash for key
  9. * @param key the key
  10. * @param value the value to put
  11. * @param onlyIfAbsent if true, don't change existing value
  12. * @param evict if false, the table is in creation mode.
  13. * @return previous value, or null if none
  14. */
  15. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  16. boolean evict) {
  17. Node<K,V>[] tab; // table的临时引用
  18. Node<K,V> p; // p是桶位置上存放的节点
  19. int n, i; // n存放table数组的长度 i是元素数组下标 即桶位置
  20. // 判断table是否为null 或者容量为空 是则初始化tablesize
  21. if ((tab = table) == null || (n = tab.length) == 0)
  22. n = (tab = resize()).length;
  23. // 桶位置上的节点为null 则创建一个节点(使用本次存放的keyvalue)
  24. // ***计算位置索引 i = (n - 1) & hash
  25. if ((p = tab[i = (n - 1) & hash]) == null)
  26. tab[i] = newNode(hash, key, value, null);
  27. else {
  28. // 如果桶位置有节点 则验证是否是同一个hash值 是则替换value
  29. Node<K,V> e; K k;
  30. if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
  31. e = p;
  32. else if (p instanceof TreeNode)
  33. // 如果桶节点元素已经是树节点 则调用putTreeVal()放入树中
  34. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  35. else {
  36. for (int binCount = 0; ; ++binCount) {
  37. if ((e = p.next) == null) {
  38. p.next = newNode(hash, key, value, null);
  39. //如果达到树化阈值 则进行树化
  40. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  41. treeifyBin(tab, hash);
  42. break;
  43. }
  44. // 如果找到key的对应节点 则break
  45. if (e.hash == hash &&
  46. ((k = e.key) == key || (key != null && key.equals(k))))
  47. break;
  48. p = e;
  49. }
  50. }
  51. // 如果映射存在 则根据onlyIfAbsent进行存储
  52. if (e != null) { // existing mapping for key
  53. V oldValue = e.value;
  54. if (!onlyIfAbsent || oldValue == null)
  55. e.value = value;
  56. afterNodeAccess(e);
  57. return oldValue;
  58. }
  59. }
  60. // 更新计数器+1
  61. ++modCount;
  62. // 如果size大于阈值 则扩容
  63. if (++size > threshold)
  64. resize();
  65. // LinkedHashMap的回调 对节点插入完成后的操作
  66. afterNodeInsertion(evict);
  67. return null;
  68. }

扩容的源码

  1. /**
  2. * Initializes or doubles table size. If null, allocates in
  3. * accord with initial capacity target held in field threshold.
  4. * Otherwise, because we are using power-of-two expansion, the
  5. * elements from each bin must either stay at same index, or move
  6. * with a power of two offset in the new table.
  7. *
  8. * @return the table
  9. */
  10. final Node<K,V>[] resize() {
  11. // 记录老容量值和阈值 生成新容量值和新阈值
  12. Node<K,V>[] oldTab = table;
  13. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  14. int oldThr = threshold;
  15. int newCap, newThr = 0;
  16. if (oldCap > 0) {
  17. // 达到最大容量 直接返回 不扩容
  18. if (oldCap >= MAXIMUM_CAPACITY) {
  19. threshold = Integer.MAX_VALUE;
  20. return oldTab;
  21. }
  22. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  23. oldCap >= DEFAULT_INITIAL_CAPACITY)
  24. // 将老容量值右移1位 即乘以2 并将阈值也翻倍
  25. newThr = oldThr << 1; // double threshold
  26. }
  27. else if (oldThr > 0) // initial capacity was placed in threshold
  28. newCap = oldThr;
  29. else { // zero initial threshold signifies using defaults
  30. newCap = DEFAULT_INITIAL_CAPACITY;
  31. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  32. }
  33. // 如果新阈值为0 则按重新计算阈值
  34. if (newThr == 0) {
  35. float ft = (float)newCap * loadFactor;
  36. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  37. (int)ft : Integer.MAX_VALUE);
  38. }
  39. threshold = newThr;
  40. @SuppressWarnings({"rawtypes","unchecked"})
  41. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  42. table = newTab;
  43. if (oldTab != null) {
  44. // 老table不为null 则遍历
  45. for (int j = 0; j < oldCap; ++j) {
  46. Node<K,V> e;
  47. if ((e = oldTab[j]) != null) {
  48. oldTab[j] = null;
  49. if (e.next == null)
  50. // 通过 e.hash & (newCap - 1)来重新计算桶位置
  51. newTab[e.hash & (newCap - 1)] = e;
  52. else if (e instanceof TreeNode)
  53. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  54. else { // preserve order
  55. Node<K,V> loHead = null, loTail = null;
  56. Node<K,V> hiHead = null, hiTail = null;
  57. Node<K,V> next;
  58. do {
  59. next = e.next;
  60. if ((e.hash & oldCap) == 0) {
  61. if (loTail == null)
  62. loHead = e;
  63. else
  64. loTail.next = e;
  65. loTail = e;
  66. }
  67. else {
  68. if (hiTail == null)
  69. hiHead = e;
  70. else
  71. hiTail.next = e;
  72. hiTail = e;
  73. }
  74. } while ((e = next) != null);
  75. if (loTail != null) {
  76. loTail.next = null;
  77. newTab[j] = loHead;
  78. }
  79. if (hiTail != null) {
  80. hiTail.next = null;
  81. newTab[j + oldCap] = hiHead;
  82. }
  83. }
  84. }
  85. }
  86. }
  87. return newTab;
  88. }

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

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

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

  2. HashMap源码解析 非原创

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

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

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

  4. 最全的HashMap源码解析!

    HashMap源码解析 HashMap采用键值对形式的存储结构,每个key对应唯一的value,查询和修改的速度很快,能到到O(1)的平均复杂度.他是非线程安全的,且不能保证元素的存储顺序. 他的关系 ...

  5. HashMap源码解析和设计解读

    HashMap源码解析 ​ 想要理解HashMap底层数据的存储形式,底层原理,最好的形式就是读它的源码,但是说实话,源码的注释说明全是英文,英文不是非常好的朋友读起来真的非常吃力,我基本上看了差不多 ...

  6. 详解HashMap源码解析(下)

    上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...

  7. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  8. 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析

    前面了解了jdk容器中的两种List,回忆一下怎么从list中取值(也就是做查询),是通过index索引位置对不对,由于存入list的元素时安装插入顺序存储的,所以index索引也就是插入的次序. M ...

  9. 【Java深入研究】9、HashMap源码解析(jdk 1.8)

    一.HashMap概述 HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.由于HashMap不是线程 ...

  10. HashMap 源码解析(一)之使用、构造以及计算容量

    目录 简介 集合和映射 HashMap 特点 使用 构造 相关属性 构造方法 tableSizeFor 函数 一般的算法(效率低, 不值得借鉴) tableSizeFor 函数算法 效率比较 tabl ...

随机推荐

  1. 【多线程】可重入锁 ReentrantLock

    java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能.而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也 ...

  2. Elasticsearch(es)介绍与安装

    ### RabbitMQ从入门到集群架构: https://zhuanlan.zhihu.com/p/375157411 可靠性高 ### Kafka从入门到精通: https://zhuanlan. ...

  3. 值得注意的: c++动态库、静态库、弱符号__attribute__((weak))以及extern之间的关系

    先说结论: ①:动态库优先级最差,如果同时有静态库和动态库,那么首先使用的是静态库函数. ②:如果只有两个或多个动态库,那么首先使用的是最开始链接的动态库函数: ③:弱符号函数在动态库中是起任何作用的 ...

  4. [补漏]shift&算法

    题意:regular number 给你一个字符串,要你输出所有(每位都符合要求的)子串,输入时告诉你每位只能填的数集. 思路: bitsetc[x]存每个数字可以存在的字符串位的二进制集合.(如3可 ...

  5. 基于Web的CAD一张图协同在线制图更新轻量级解决方案[示例已开源]

    背景 之前相关的博文中介绍了如果在Web网页端展示CAD图形(唯杰地图云端图纸管理平台 https://vjmap.com/app/cloud),有不少朋友问,能不能实现一个协同的功能,实现不同部门不 ...

  6. QC快速充电

    QC快充 一.高通QC快充的介绍 二.识别充电类型的芯片介绍 三.QC充电曲线 四.如何在log中看QC充电类型 五.QC3识别错误 六.波形图 一.高通QC快充的介绍 高通QC快充技术,又称Quic ...

  7. java提前工作、第一个程序

    java提前工作 我们学习编程肯定会 运用到相应的软件 在这里 我个人推荐 eclipse.idea 这里的软件呢 都是用我们的java编程出来的,那它也需要用java来支持他的开发环境 这里就运用到 ...

  8. 皓远的第二次博客作业(最新pta集,链表练习及期中考试总结)

    前言: 知识点运用:正则表达式,有关图形设计计算的表达式和算法,链表的相关知识,Java类的基础运用,继承.容器与多态. 题量:相较于上次作业,这几周在java方面的练习花了更多的精力和时间,所要完成 ...

  9. 【Spring】AOP实现原理(二):Advisor获取

    @EnableAspectJAutoProxy @EnableAspectJAutoProxy注解可以用来开启AOP,那么就从@EnableAspectJAutoProxy入手学习一下Spring A ...

  10. numpy中的np.round()取整的功能和注意

    numpy中的np.round()取整的功能和注意 功能 np.round() 是对浮点数取整的一个函数,一般的形式为 np.round(a, b),其中a为待取整的浮点数,b为保留的小数点的位数 注 ...