主要针对jdk1.8源码解读

Q:HashMap原理,内部数据结构?

A:底层时使用哈希表(数组+链表),当链表过长会将链表转成 红黑树以实现O(logn)时间复杂度内查找.

Q:HashMap里put过程?

A:对key求Hash值,然后再计算下标.如果没有碰撞,直接放入桶中.如果碰撞了,以链表的方式链接到后面.如果链表长度超过阀值(TREEIFY_THRESHOLD == 8),就把链表转成红黑树.如果节点已经存在就替换旧值.如果桶满了(容量*负载因子),就需要resiaze.

Q:HashMap中hash函数怎么实现的?

A:高16bit不变,低16bit和高16bit做了一个异或,(n-1) & hash --> 下标

Q:HashMap怎样解决冲突,讲一下扩容过程,假如一个值在原数组中,现在移动了新数组,位置肯定改变了,那是怎么定位在这个值新数组中的位置?

A:将新节点加到链表后,容量扩充为原来的两倍,然后对每个节点重新计算哈希值.这个值只可能在两个地方,一种是原下标的位置,另一种是在下标为<原下标+原容量>的位置

以上是常问问题,那大部分人面对这些问题感觉非常不爽,为什么不爽,哪里不爽,下面让你爽起来

上面说过,HashMap是数组和链表组成的,如图

接下来一行一行撕源码:

    /*** 默认初始化容量- 大小必须是2的幂*/static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/***最大容量 2的30次幂*/static final int MAXIMUM_CAPACITY = 1 << 30;/*** 负载因子 0.75*/static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** 链表节点数*/static final int TREEIFY_THRESHOLD = 8;/*** The bin count threshold for untreeifying a (split) bin during a* resize operation. Should be less than TREEIFY_THRESHOLD, and at* most 6 to mesh with shrinkage detection under removal.* 当某个桶节点数量小于6时,会转换为链表,前提是它当前是红黑树结构*/static final int UNTREEIFY_THRESHOLD = 6;/*** The smallest table capacity for which bins may be treeified.* (Otherwise the table is resized if too many nodes in a bin.)* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts* between resizing and treeification thresholds.* 当整个hashMap中元素数量大于64时,也会进行转为红黑树结构*/static final int MIN_TREEIFY_CAPACITY = 64;/*** 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关键字表示该属性不能被序列化*/transient Node<K, V>[] table;/*** Holds cached entrySet(). Note that AbstractMap fields are used* for keySet() and values().* 将数据转换成set的另一种存储形式,这个变量主要用于迭代功能*/transient Set<Map.Entry<K, V>> entrySet;/*** The number of key-value mappings contained in this map.* 元素size*/transient int size;/*** The number of times this HashMap has been structurally modified* Structural modifications are those that change the number of mappings in* the HashMap or otherwise modify its internal structure (e.g.,* rehash).  This field is used to make iterators on Collection-views of* the HashMap fail-fast.  (See ConcurrentModificationException).* 统计该map修改的次数*/transient int modCount;/*** The next size value at which to resize (capacity * load factor).** @serial* 临界值,也就是元素数量达到临界值时,会进行扩容*/// (The javadoc description is true upon serialization.// Additionally, if the table array has not been allocated, this// field holds the initial array capacity, or zero signifying// DEFAULT_INITIAL_CAPACITY.)int threshold;/*** The load factor for the hash table.** @serial* 也是负载因子,只不过这个是变量*/final float loadFactor;/*** 有参构造*/public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}/*** 构造方法初始化默认16,并加载负载因子0.75*/public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}/*** Constructs a new <tt>HashMap</tt> with the same mappings as the* specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with* default load factor (0.75) and an initial capacity sufficient to* hold the mappings in the specified <tt>Map</tt>.** @param   m the map whose mappings are to be placed in this map* @throws  NullPointerException if the specified map is null*/public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}

直接看put方法

   /*** Associates the specified value with the specified key in this map.* If the map previously contained a mapping for the key, the old* value is replaced.* 将指定的值与此映射中的指定键相关联,如果map以前包含该键的映射,则为旧映射价值被取代。* @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with <tt>key</tt>, or*         <tt>null</tt> if there was no mapping for <tt>key</tt>.*         (A <tt>null</tt> return can also indicate that the map*         previously associated <tt>null</tt> with <tt>key</tt>.)*/public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

这个鬼putVal方法里面还调了hash(),我们看一下hash()

  /*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower.  Because the table uses power-of-two masking, sets of* hashes that vary only in bits above the current mask will* always collide. (Among known examples are sets of Float keys* holding consecutive whole numbers in small tables.)  So we* apply a transform that spreads the impact of higher bits* downward. There is a tradeoff between speed, utility, and* quality of bit-spreading. Because many common sets of hashes* are already reasonably distributed (so don't benefit from* spreading), and because we use trees to handle large sets of* collisions in bins, we just XOR some shifted bits in the* cheapest possible way to reduce systematic lossage, as well as* to incorporate impact of the highest bits that would otherwise* never be used in index calculations because of table bounds.** 例如:* key.hashcode = 0110 0011 0101 0101 0010 1010 0111 0101* 低16位与高16位做异或运算,充分运算,减少碰撞* 如果n很小,那么只有低4位参与运算,高位没参与,随即不太够* 0110 0011 0101 0101 0010 1010 0111 0101* 0000 0000 0000 0000 0110 0011 0101 0101* --------------------------------------------* 0110 0011 0101 0101 0100 1001 0010 0000**/static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

下面回到putVal方法,开篇说过,map由数组和链表构成,那么数组里存的即是node节点,看一下node的静态类node里面存了key,value,还有hash值,和next节点,

    /*** 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> {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;}public final K getKey() {return key;}public final V getValue() {return value;}public final String toString() {return key + "=" + value;}public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}/*** Implements Map.put and related methods** @param hash         hash for key* @param key          the key* @param value        the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict        if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {//tab 哈希数组Node<K, V>[] tab;//p 首节点Node<K, V> p;//n 长度;i 数组下标int n, i;//懒加载,调用put时候扩容初始化大小if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//(n-1)&hash和取模是一样的,按位与计算效率更高,//至于为什么是n-1,因为数组的长的一直是2的n次幂//    16 = 1 0000;32=10 0000//那么 n-1 = 16-1 = 15 = 0111//如果hash = 0001 0111 0101 0111 0111 1111//           0000 0000 0000 0000 0000 1111//--------------------------------------------------//           0000 0000 0000 0000 0000 1111//这样按位与的话最大也就是1111也就是15和取模的结果一样,效率会高很多//然后计算p在数组里如果为null,就直接在newNode方法里赋值if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);//如果发生hash冲突else {//临时节点Node<K, V> e;//当前节点的keyK k;//如果key存在直接覆盖valueif (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 1sttreeifyBin(tab, hash);break;}//key存在直接覆盖valueif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;//这个treshold是n*0.75if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

下面来看resize方法,注释的解释大概就是初始化2倍table大小,如果为null,则根据字段阈值中保存的初始容量目标进行分配,另外,因为我们用2的幂进行扩容,元素必须呆在同样的索引里,或者在新表中以2的幂个偏移量移动,说白了就是要么呆在原地,要么就是原来的索引加上扩容的size得到的索引(个人理解)。

   /*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table*/final Node<K, V>[] resize() {//把没插入之前的数组作为oldTabNode<K, V>[] oldTab = table;//old长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//old的临界值int oldThr = threshold;//初始化new的长度和临界值int newCap, newThr = 0;//old大于0也就是说不是首次初始化,因为hashmap用的是懒加载if (oldCap > 0) {//大于最大值if (oldCap >= MAXIMUM_CAPACITY) {//临界值为整数的最大值threshold = Integer.MAX_VALUE;return oldTab;}//标记!!,其他情况,扩容两倍,并且扩容后的长度要小于最小值,old长度也要大于16else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)//临界值扩容为old的临界值2倍newThr = oldThr << 1; // double threshold} else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {// 首次初始化,给与默认的值 zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;//临界值等于容量*负载因子newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//此处的if为上面标记!!的补充,也就是初始化时容量小于默认值16的,此时newThr没有赋值if (newThr == 0) {//new的临界值float ft = (float) newCap * loadFactor;//判断是否new容量是否大于最大值,临界值是否大于最大值newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?(int) ft : Integer.MAX_VALUE);}//把上面各种情况分析出的临界值,在此处真正进行改变,也就是容量和临界值都改变了。threshold = newThr;//表示忽略该警告@SuppressWarnings({"rawtypes", "unchecked"})//初始化Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];//赋予当前的tabletable = newTab;//此处自然是把old中的元素,遍历到new中if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {//临时变量Node<K, V> e;//当前哈希桶的位置值不为null,也就是数组下标处有值,因为有值表示可能会发生冲突if ((e = oldTab[j]) != null) {//把已经赋值之后的变量置位null,当然是为了好回收,释放内存oldTab[j] = null;//如果下标处的节点没有下一个元素if (e.next == null)//把该变量的值存入newCap中,e.hash & (newCap - 1)并不等于jnewTab[e.hash & (newCap - 1)] = e;//该节点为红黑树结构,也就是存在哈希冲突,该哈希桶中有多个元素else if (e instanceof TreeNode)//把此树进行转移到newCap中((TreeNode<K, V>) e).split(this, newTab, j, oldCap);else { // preserve order//此处表示为链表结构,同样把链表转移到newCap中,就是把链表遍历后,把值转过去,在置位nullNode<K, V> loHead = null, loTail = null;Node<K, V> hiHead = null, hiTail = null;Node<K, V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;} else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}//返回扩容后的hashMapreturn newTab;}

再看下get()方法

 /*** Returns the value to which the specified key is mapped,* or {@code null} if this map contains no mapping for the key.* <p>* <p>More formally, if this map contains a mapping from a key* {@code k} to a value {@code v} such that {@code (key==null ? k==null :* key.equals(k))}, then this method returns {@code v}; otherwise* it returns {@code null}.  (There can be at most one such mapping.)* <p>* <p>A return value of {@code null} does not <i>necessarily</i>* indicate that the map contains no mapping for the key; it's also* possible that the map explicitly maps the key to {@code null}.* The {@link #containsKey containsKey} operation may be used to* distinguish these two cases.** @see #put(Object, Object)*/public V get(Object key) {Node<K, V> e;//也是调用getNode方法来完成的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) {//first 头结点,e 临时变量,n 长度,k keNode<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;}

再看看remove()方法

 /*** Removes the mapping for the specified key from this map if present.** @param key key whose mapping is to be removed from the map* @return the previous value associated with <tt>key</tt>, or* <tt>null</tt> if there was no mapping for <tt>key</tt>.* (A <tt>null</tt> return can also indicate that the map* previously associated <tt>null</tt> with <tt>key</tt>.)*/public V remove(Object key) {//临时变量Node<K, V> e;//调用removeNode(hash(key), key, null, false, true)进行删除,第三个value为null,//表示,把key的节点直接都删除了,不需要用到值,如果设为值,则还需要去进行查找操作return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;}/*** Implements Map.remove and related methods** @param hash       hash for key* @param key        the key* @param value      the value to match if matchValue, else ignored* @param matchValue if true only remove if value is equal* @param movable    if false do not move other nodes while removing* @return the node, or null if none* <p>* 第一参数为哈希值,* 第二个为key,* 第三个value,* 第四个为是为true的话,* 则表示删除它key对应的value,不删除key,第四个如果为false,则表示删除后,不移动节点*/final Node<K, V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {//tab 哈希数组,p 数组下标的节点,n 长度,index 当前数组下标Node<K, V>[] tab;Node<K, V> p;int n, index;//哈希数组不为null,且长度大于0,然后获得到要删除key的节点所在是数组下标位置if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {//nodee 存储要删除的节点,e 临时变量,k 当前节点的key,v 当前节点的valueNode<K, V> node = null, e;K k;V v;//如果数组下标的节点正好是要删除的节点,把值赋给临时变量nodeif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;//也就是要删除的节点,在链表或者红黑树上,先判断是否为红黑树的节点else if ((e = p.next) != null) {if (p instanceof TreeNode)//遍历红黑树,找到该节点并返回node = ((TreeNode<K, V>) p).getTreeNode(hash, key);else { //表示为链表节点,一样的遍历找到该节点do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}//注意,如果进入了链表中的遍历,那么此处的p不再是数组下标的节点,而是要删除结点的上一个结点p = e;} while ((e = e.next) != null);}}//找到要删除的节点后,判断!matchValue,我们正常的remove删除,!matchValue都为trueif (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {//如果删除的节点是红黑树结构,则去红黑树中删除if (node instanceof TreeNode)((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);//如果是链表结构,且删除的节点为数组下标节点,也就是头结点,直接让下一个作为头else if (node == p)tab[index] = node.next;else//为链表结构,删除的节点在链表中,把要删除的下一个结点设为上一个结点的下一个节点p.next = node.next;//修改计数器++modCount;//长度减一--size;//此方法在hashMap中是为了让子类去实现,主要是对删除结点后的链表关系进行处理afterNodeRemoval(node);//返回删除的节点return node;}}//返回null则表示没有该节点,删除失败return null;}

以上有部分参考其他大神文章,也有一些个人理解,后续还有更新,如有理解误区请大家提出来,一起探讨。

疯狂解读HashMap源码相关推荐

  1. 【JavaMap接口】HashMap源码解读实例

    这里写自定义目录标题 1.HashMap源码 2.HashMap使用 1.HashMap源码 解读HashMap的源码 执行构造器 new HashMap() 初始化加载因子 loadfactor = ...

  2. HashMap源码解读—Java8版本

    [手撕源码系列]HashMap源码解读-Java8版本 一.HashMap简介 1.1 原文 1.2 翻译 1.3 一语中的 1.4 线程安全性 1.5 优劣分析 二.定义 三.数据结构 四.域的解读 ...

  3. HashMap源码解读(中篇)

    文章目录 前言 一.进入JDK中的源码(InteliJ IDEA为例) 二.HashMap的结构 三.源码解读 3.1 属性解读 3.2 put方法解读 3.2.1 HashMap中的hash方法 3 ...

  4. HashMap源码解读

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  5. 搞懂 Java HashMap 源码

    HashMap 源码分析 前几篇分析了 ArrayList , LinkedList ,Vector ,Stack List 集合的源码,Java 容器除了包含 List 集合外还包含着 Set 和 ...

  6. HashMap 源码深度分析

    HashMap 源码分析 在Map集合中, HashMap 则是最具有代表性的,也是我们最常使用到的 Map 集合.由于 HashMap 底层涉及了很多的知识点,可以比较好的考察一个人的Java的基本 ...

  7. HashMap源码实现分析

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 一.前言 HashMap 顾名思义,就是用hash表的原理 ...

  8. Java源码详解二:HashMap源码分析--openjdk java 11源码

    文章目录 HashMap.java介绍 1.HashMap的get和put操作平均时间复杂度和最坏时间复杂度 2.为什么链表长度超过8才转换为红黑树 3.红黑树中的节点如何排序 本系列是Java详解, ...

  9. 【用故事解读 MobX源码(一)】 autorun

    ================前言=================== 初衷:网上已有很多关于 MobX 源码解读的文章,但大多阅读成本甚高.本人在找文章时对此深有体会,故将以系列故事的方式展现源 ...

最新文章

  1. 小蚂蚁学习mysql性能优化(4)--SQL以及索引优化--具体一些优化方法
  2. meetup_如何使用标准库和Node.js构建Meetup Slack机器人
  3. CSS 盒子模型(转)
  4. 【python】lambda函数
  5. 如何设置访问共享弹出窗口
  6. 【医疗影像处理】dcm2niix python3 使用
  7. StuQ Android 会员学习计划|帮你成为更优秀的 Android 工程师
  8. 经典排序算法(四)--基数排序Radix Sort
  9. axure8 Mac破解版+汉化包
  10. H5混合APP开发框架
  11. QT学习笔记(六)——①进度条可拖动、点击②有暂停按钮 的视频播放器
  12. ps——霓虹灯字体效果
  13. deflate与gzip
  14. 两位8421BCD码加法器的设计与实现
  15. egg-views-ejs
  16. 准备启动一个开源项目 - 技术族谱 - 先期利用Goolge云计算平台
  17. Python进阶小技巧2
  18. 显示桌面的图标不见了 怎么显示出来
  19. 查找缺失的DLL工具Dependency Walker
  20. 搜狗输入法如何使用翻译功能--win10专业版

热门文章

  1. 从输入网址到页面呈现的详细过程
  2. 六西格玛管理四个核心理念
  3. 【计算机毕业设计】511社区维修平台
  4. ovs hot upgrade
  5. python里while 1是什么意思_关于python:“ while 1”和“ while True”之间有什么区别?...
  6. 舆情智慧决策闭环管理
  7. 地图可视化:零编程,BDP轻松制作动态轨迹地图!
  8. 自然语言处理实战项目2-文本关键词抽取和关键词分值评估
  9. UMTS HLR VLR GR MGW AR AUC SG PESQ TrFO
  10. 看到一句话,感受良多