HashMap
学习链接:HashMap全B站最细致源码分析课程,看完月薪最少涨5k!_哔哩哔哩_bilibili
github源码链接:LiLi8080/map (github.com)
一、什么是Hash
1.1 基本概念:
Hash: 也称为散列、哈希,对应英文都是Hash。
基本原理: 把任意长度的输入通过Hash算法变成固定长度的输出。
Hash算法:上述映射规则就是Hash算法
Hash值: 原始数据映射后的二进制串就是Hash值
1.2 Hash特点
1. 从hash值不可以反向推到出原始数据
2. 输入数据的微小变化会得到完全不同的hash值,相同数据会得到相同的值
3.哈希算法的执行效率要高效,长的文本也能快速计算出hash值
4. hash算法的冲突概率要小
二、HashMap原理

HashMap继承体系

HashMap底层数据结构
转换成红黑树的条件: 链表长度>8 即链表程度>=9并且Node数组中的总元素个数>=64时 该链表讲转换成红黑树
2.1 put数据原理分析

步骤详细分析:
- map.put("暴躁”,“小刘") // HashMap put数据
- put方法执行流程
- 获取”暴躁“字符串的hash值
- 扰动函数扰动hash值,得到更均匀散列的hash值(详细方法后续讲解,如果不扰动,hash碰撞概率会增加)
- 构造Node对象 ( 用要插入的数据初始化Node对象)
- Node对象
static class Node<K,V> implements java.util.Map.Entry<K,V> {
final int hash;
final K key;
V value;
java.util.HashMap.Node<K,V> next;
Node(int hash, K key, V value, java.util.HashMap.Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
...
}
- Node对象
- 路由算法,找到Node存放的数组位置
- 路由算法原理: (table.length-1)&node.hash = (16-1) & 1122 =0000 0000 1111 & 0100 0110 0010 = 2
- table.length: Node数组的长度(Node数组的长度一定为2的^x 因为2^x数据-1后二进制的最末尾的x个数一定为1 再与node.hash相与 范围一定在0-table.length-1之间,相当于对table.length取余)
2.2 Hash碰撞
多个Node经过hash映射函数映射到Node数组的同一个Node链表中
2.3 什么是链化
将那些发生Hash碰撞的Node元素链接到一个Node链表中
2.4 JDK为什么引入红黑树
因为Node链表元素过多 查询效率低下,引入红黑树后查询效率变为Logn (虽然会增加插入时间 但查询效率大大提高)
2.5 HashMap扩容原理
(59条消息) JDK 1.8 HashMap扩容原理_u013171997的专栏-CSDN博客
三、源码
3.1 核心属性分析
// 缺省时table的大小 即缺省时Node数组的长度 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 // table的最大长度 即hash表中Node数组的最大长度 static final int MAXIMUM_CAPACITY = 1 << 30; // 缺省时负载因子的大小 即没有调用构造函数给负载因子赋值 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 树化域值 即当Node数组中某个链表中元素数量大于8时可能进行树化 static final int TREEIFY_THRESHOLD = 8; // 树降级域值 即Node数组中某个链表中元素个数小于6时将对树进行链表化 static final int UNTREEIFY_THRESHOLD = 6; // 树化的另一个域值 当hash表中所有元素个数大于64才可能对某个大于8个元素的链表树化 static final int MIN_TREEIFY_CAPACITY = 64; /* Field */ // hash表 transient java.util.HashMap.Node<K,V>[] table; transient Set<Entry<K,V>> entrySet; // 当前hash表中元素个数 transient int size; // 当前hash表结构修改次数 往hash表中增加/删除某个元素代表结构修改 但替换某个元素不算 transient int modCount; // 扩容域指,当hash表中元素个数超过域值时触发扩容。 int threshold; // 负载因子 threshold = loadFactor*capacity capacity是hash表中Node数组长度 final float loadFactor;
3.2 构造方法分析
/* 构造方法源码分析 */ public HashMap(int initialCapacity, float loadFactor) { // 就是做了一些校验 // capacity必须>0且<=MAXIMUM_CAPACITY 当输入capacity小于时抛出异常 // 大于MAXIMUM_CAPACITY时直接初始化为MAXIMUM_CAPACITY if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // loadFactor必须>0的数 否则抛异常 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; // hash表中Node数组的长度必须是2^x,但传入的initialCapacity不一定是2^x 因此需要tableSizeFor去处理 this.threshold = tableSizeFor(initialCapacity); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(java.util.Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } /* * 作用:返回一个>=当前cap的一个数字 ,并且这个数字一定是一个2的次方数 * * 示例: * cap = 10 * n = 10-1 = 9 * 0b1001 | 0b0100 => 0b1101 * 0b1101 | 0b0011 => 0b1111 * 0b1111 | 0b0000 => 0b1111 * 0b1111 | 0b0000 => 0b1111 * 0b1111 | 0b0000 => 0b1111 = 15 * * return n+1 = 15+1 = 16 = 2^4 * * 实际意义: * 将某个cap = 0001 1101 1100 ==> 0001 1111 1111 +1 = 0010 0000 0000 * return 0010 0000 0000 这个数一定是>= cap的2的次方数 */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
3.3 put源码分析
/* put源码分析 */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } /* * 作用: 让key的hash值的高16位也参与运算 即hash扰动函数 * 当key为null时,hash(key) =0 ,路由算法查询在哈希表Node数组位置也为0,因此key=null的数据将插在hash表Node[0]的链表中 * 如果不用扰乱函数 ,每次路由只有末尾n位参与有效运算 */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } /* * onlyIfAbsent : =true时,如果散列表中有此key的数据,则不进行插入操作 * =false时,散列表中有此数据则进行替换 */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // tab:引用当前hashMap的散列表 // p:表示当前散列表的元素 // n:表示散列表数组的长度 // i:表示路由寻址结果 java.util.HashMap.Node<K,V>[] tab; java.util.HashMap.Node<K,V> p; int n, i; // 延迟初始化,第一次putVal才会初始化hashMap对象中最耗费内存的散列表 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // i = (n - 1) & hash 路由算法 i=路由找到的hash列表Node数组的下标 // 即要将当前元素p插入到Node[i]中 // 情况一:Node[i] == null 直接插入 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 情况二: Node[i]!=null else { // e: 不为null表示找到了一个与当前要插入的key-value一致的key的元素 // k: 表示临时的一个key java.util.HashMap.Node<K,V> e; K k; // 表示桶位中的该元素与你当前插入的元素的key完全一致,表示后续需要进行替换操作 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 是红黑树时.... else if (p instanceof java.util.HashMap.TreeNode) e = ((java.util.HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 是链表时xxx else { // 链表的头元素与我们要插入的元素的key不一致 for (int binCount = 0; ; ++binCount) { // 条件成立说明迭代到最后一个元素也没找到与你要插入的元素key相同的元素 // 需要将插入元素插入到当前链表末尾 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; } // 条件成立:说明找到与要插入元素相同的key的node,需要进行替换操作 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // e!=null说明找到了相同key的node 需要进行替换 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; // 替换完后直接返回 } } // 表示元素进行的是插入而不是替换操作 即替换操作modCount不+1 ++modCount; // 插入新元素size++,如果自增后元素值大于threshold则进行扩容操作 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
3.4 扩容分析

规律: 扩容后的哈希表中相同桶中的元素一定也在原始未扩容的相同桶中
但再原始未扩容的相同桶中的数据扩容后可能不在新table的相同桶中
/* resize源码分析 */ /* * 作用:扩容操作。为了解决h哈希冲突d导致的链化y影响查询效率的问题,扩容后会缓解该问题 */ final java.util.HashMap.Node<K,V>[] resize() { // 引用扩容前的hash表 java.util.HashMap.Node<K,V>[] oldTab = table; // 表示扩容之前table数组的长度 int oldCap = (oldTab == null) ? 0 : oldTab.length; // oldThr:表示扩容之前的扩容域指,触发本次扩容的域指 int oldThr = threshold; // newCap: 扩容之后table数组的大小 // newThr:扩容之后下次再次触发k扩容的条件 int newCap, newThr = 0; // 计算newCap 和 newThr // 条件成立:说明hashMap中的散列表已经初始化过了,这是一次正常扩容 if (oldCap > 0) { // 扩容之前的table数组大小已经达到了最大值,不扩容 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // oldCap左移一位s实现数值翻倍,并赋值给newCap newCapx小于数组最大值限制q且扩容之前的域值>=16 // 这种情况下 下次扩容的域指等于当前阈值翻倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // oldCap == 0 说明hashmap的散列表是null // 1. new HahMap(initCap,loadFactor) // 2. new HashMap(initCap) // 3. new HashMap(map) 会更改oldThr的值 // 4. new HashMap() 不会更改oldThr的 // 第一次初始化 置newCap = oldThr 因为oldThr是通过tableSizeFor(cap)计算所得 一定为2的N次方 else if (oldThr > 0) newCap = oldThr; // oldCap == 0 && oldThr == 0 // new HashMap() 构造对象 只初始化loadFactor else { newCap = DEFAULT_INITIAL_CAPACITY; // 16 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 12 } // newThr为零时,通过newCaph和loadFactor计算出一个newThr if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; // 根据newCap创建新的更大的数组 @SuppressWarnings({"rawtypes","unchecked"}) java.util.HashMap.Node<K,V>[] newTab = (java.util.HashMap.Node<K,V>[])new java.util.HashMap.Node[newCap]; table = newTab; // 说明扩容前table中可能有数据 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { // 当前node节点 java.util.HashMap.Node<K,V> e; // 说明当前桶中有数据,但数据具体是单个数据 / 链表/红黑树不确定 if ((e = oldTab[j]) != null) { // 方便JVM GC回收内存 oldTab[j] = null; // 第一种情况:当前桶中只有一个元素,从未发生过碰撞,则直接计算出当前元素应该存放再新table中的位置 // 然后扔进去 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 第二种情况 当前节点已经树化 ... else if (e instanceof java.util.HashMap.TreeNode) ((java.util.HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 第三种情况 已经形成链表 else { // 低位链表:存放扩容之后的数组的下标位置,与当前s数组的下标位置相同 java.util.HashMap.Node<K,V> loHead = null, loTail = null; // 高位链表:存放扩容之后的数组的下标位置为当前数组的下标位置+扩容之前数组的长度 java.util.HashMap.Node<K,V> hiHead = null, hiTail = null; java.util.HashMap.Node<K,V> next; do { next = e.next; // oldIndex = e.hash & (oldCap-1) 当e.hash & oldCap == 0 证明e.hash在oldCap的最高位为0 转换成新数组的下标将无变化 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // oldIndex = e.hash & (oldCap-1) 当e.hash & oldCap == 1 证明e.hash在oldCap的最高位为1 转换成新数组的下标将增加oldCap else { if (hiTail == null) hiHead = e; else hiTail.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; } } } } } return newTab; }
3.5 get源码分析
/* get源码分析 */ public V get(Object key) { java.util.HashMap.Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final java.util.HashMap.Node<K,V> getNode(int hash, Object key) { // tab: 当前散列表 // first:桶中首元素 // e: 临时存放node 的元素 // n : 当前散列表数组长度 java.util.HashMap.Node<K,V>[] tab; java.util.HashMap.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 && ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 桶中元素不只一个 if ((e = first.next) != null) { // 桶中元素已形成树结构 查找树 if (first instanceof java.util.HashMap.TreeNode) return ((java.util.HashMap.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; }
3.6 remove源码分析
/* remove 源码分析 */ public V remove(Object key) { java.util.HashMap.Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } @Override public boolean remove(Object key, Object value) { return removeNode(hash(key), key, value, true, true) != null; } final java.util.HashMap.Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { // tab: 当前散列表 // p: 当前node元素 // n: 散列表数组长度 // index:寻址结果 即寻址到的桶位 java.util.HashMap.Node<K,V>[] tab; java.util.HashMap.Node<K,V> p; int n, index; // 条件为真: 哈希表不为空 且查找到的桶位有数据 if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { java.util.HashMap.Node<K,V> node = null, e; K k; V v; // 第一种情况: 当前桶的首元素即为要删除的元素 用node标记 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; // 第二种情况 : 当前桶首元素不是要删除的元素,桶中不只一个元素 else if ((e = p.next) != null) { // 当前桶为红黑树 利用红黑树方法获取删除node if (p instanceof java.util.HashMap.TreeNode) node = ((java.util.HashMap.TreeNode<K,V>)p).getTreeNode(hash, key); // 档期桶为链表 遍历链表查找要删除的元素并用node标记 else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } // node标记不为空 即查找到了要删除的元素 if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { // 要删除的元素在红黑树中 利用红黑树方法删除 if (node instanceof java.util.HashMap.TreeNode) ((java.util.HashMap.TreeNode<K,V>)node).removeTreeNode(this, tab, movable); // 要删除的元素是桶中链表的首元素 else if (node == p) tab[index] = node.next; // 要删除的元素是桶中链表的中间元素 else p.next = node.next; ++modCount; //删除操作也要更新modCount --size; afterNodeRemoval(node); return node; // 返回删除的元素 } } return null; }
3.7 replace源码分析
/* replace源码分析 */ @Override public boolean replace(K key, V oldValue, V newValue) { java.util.HashMap.Node<K,V> e; V v; if ((e = getNode(hash(key), key)) != null && ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) { e.value = newValue; afterNodeAccess(e); return true; } return false; } @Override public V replace(K key, V value) { java.util.HashMap.Node<K,V> e; if ((e = getNode(hash(key), key)) != null) { V oldValue = e.value; e.value = value; afterNodeAccess(e); return oldValue; } return null; }

浙公网安备 33010602011771号