HashMap源码分析_JDK1.8版本
来源:互联网 发布:知乎一小时全集 mobi 编辑:程序博客网 时间:2024/06/12 21:17
HashMap源码分析_JDK1.8版本
声明
文章均为本人技术笔记,转载请注明出处
[1] https://segmentfault.com/u/yzwall
[2] blog.csdn.net/j_dark/
HashMap声明
public class HashMap
HashMap结构图示
HashMap基本数据结构
- HashMap本质是一个散列表,存储元素为键值对;
- HashMap继承AbstractMap,实现了Map、Cloneable、java.io.Serializable接口;
- HashMap的是线程不安全的,它的key、value都可以为null;
final int loadFacotr
static final float DEFAULT_LOAD_FACTOR
: 默认装填因子0.75,如果当前键值对个数 >= HashMap最大容量*装填因子,进行rehash操作;int threshold
static final int TREEIFY_THRESHOLD
: JDK1.8 新加,Entry链表最大长度,当桶中节点数目大于该长度时,将链表转成红黑树存储;static final int UNTREEIFY_THRESHOLD
:JDK1.8 新加,当桶中节点数小于该长度,将红黑树转为链表存储;static final int DEFAULT_INITIAL_CAPACITY
: 默认键值队个数为16;transient Node<K, V>[] table
:键值对数组,长度动态增加,但是总为2的幂;用transient
修饰表示对象序列化时该字段不可被序列化;
HashMap键值对描述:Node<K,V>
JDK1.6用Entry描述键值对,JDK1.8中用Node代替Entry;
static class Node<K,V> implements Map.Entry<K,V> { // hash存储key的hashCode final int hash; // final:一个键值对的key不可改变 final K key; V value; // 一个桶中的键值对用链表组织 Node<K, V> next; ...}
HashMap中键值对的存储形式为链表节点,hashCode相同的节点(位于同一个桶)用链表组织;
public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value);}
键值对元素hashCode = key的hashCode与value的hashCode,高16位与低16位按位异或运算;
红黑树:TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
JDK1.8新增,用来支持桶的红黑树结构实现
HashMap重要方法分析
HashMap添加/更新键值对:put/putVal方法
public V put(K key, V value)
内部调用putVal方法实现;
public V put(K key, V value) { // 倒数第二个参数false:表示允许旧值替换 // 最后一个参数true:表示HashMap不处于创建模式 return putVal(hash(key), key, value, false, true);}
putVal方法分析:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 槽数组未初始化或者未扩容,先调用resize()扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; /** * Hash函数,(n - 1) & hash 计算key将被放置的槽位; * (n - 1) & hash 本质上是hash % n,位运算更快 */ if ((p = tab[i = (n - 1) & hash]) == null) // 空桶,创建新的键值对节点,放入槽数组中; tab[i] = newNode(hash, key, value, null); // 键值对已在对应桶中 else { Node<K,V> e; K k; // 与桶中首元素比较,如果key不同发生Hash冲突,在桶中添加新元素 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 treeifyBin(tab, hash); break; } // 链表节点的<keym, value>与put操作<key, value>相同时,不做重复操作,跳出循环 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 找到 or 新建一个key和hashCode与插入元素相等的键值对,进行put操作 if (e != null) { // existing mapping for key V oldValue = e.value; /** * onlyIfAbsent为false或旧值为null时,允许替换旧值 * 否则无需替换 */ if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } // 更新结构化修改信息 ++modCount; // 键值对数目超过阈值时,进行rehash if (++size > threshold) resize(); afterNodeInsertion(evict); return null;}
散列分布策略
键值对<key ,value>的槽位 = (容量 - 1) & hash(key)
+ 键值对槽位是键值对在tab数组的索引,本质上 = hash(key) % 容量,位运算速度更快;
+ 本质上是除数取余法,尽可能的散列均匀;
Hash函数
// in HashMapstatic final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
- 计算key的hashCode, h = Objects.hashCode(key);
h >>> 16
表示对h无符号右移16位,高位补0;然后h与h >>> 16按位异或;
HashMap更新旧键值对 or 添加新键值对的核心思想:
1. 根据键值对key的hashCode计算键值对的在HashMap中槽位,
2. 判断是否空桶 Or 是否发生Hash冲突(与桶中首元素不同)
3. 解决Hash冲突:根据桶组织形式是红黑树 Or 链表进行对应插入操作;
+ 链表形式完成插入后,检查是否超过链表阈值,超过将链表->红黑树;
4. 最后检查键值对总数是否超过阈值,超过调用resize()进行rehash操作;
HashMap删除键值对:remove/removeNode方法
remove方法分析
public V remove(Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;}
remove()
方法内部调用removeNode()
方法实现
removeNode方法分析:
final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (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为true:表示必须value相等才进行删除操作 * matchValue为false:表示无须判断value,直接根据key进行删除操作 */ if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { // 桶为红黑数结构,删除节点 if (node instanceof TreeNode) // movable参数用于红黑树操作 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); // 待删除节点是桶链表表头,将子节点放进桶位 else if (node == p) tab[index] = node.next; // 待删除节点在桶链表中间 else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } // 待删除元素不存在,返回null return null;}
HashMap访问键值对:get/getNode方法
get方法
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value;}
get
方法通过指定key获得对应键值对,内部调用getNode
方法实现;
getNode方法
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;}
getNode
方法查找过程和putVal
一样,先查找对应桶的首元素,然后根据红黑树结构 Or 链表结构对应查找;
HashMap重散列操作:resize方法
final Node<K,V>[] resize() { // 保存旧table,容量,阈值 Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 旧table容量已超过最大容量,更新阈值为Integer.MAX_VALUE if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 调整新容量为旧容量2倍,左移一位实现 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // oldCap == 0 && oldThr > 0 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; // oldCap == 0 && oldThr == 0 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; 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]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 红黑树桶进行rehash操作 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 链表桶进行rehash操作 // 根据e.hash & oldCap)是否为0把链表分成两个链表,进行rehash else { // preserve order Node<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; else loTail.next = e; loTail = e; } 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;}
当键值对总数超过阈值threshold
, HashMap通过resize
方法实现重散列/rehash
HashMap调整容量:tableSizeFor()
static final int tableSizeFor(int cap)
:得到>=cap的2的最小幂值;
由指定容量参数的构造器调用,计算rehash阈值threshold;
参考
[1] http://www.cnblogs.com/leesf456/p/5242233.html
[2] http://www.cnblogs.com/ToBeAProgrammer
- HashMap源码分析_JDK1.8版本
- ConcurrentHashMap源码分析_JDK1.8版本
- JDK1.8 HashMap源码分析
- JDK1.8 HashMap源码分析
- 【jdk1.8】HashMap源码分析
- jdk1.8 hashMap源码分析
- JDK1.8 HashMap 源码分析
- JDK1.8 HashMap源码分析
- HashMap源码分析 JDK1.8
- jdk1.8 HashMap源码分析
- HashMap各个版本分析
- Java 8 中HashMap源码分析
- HashMap源码分析(jdk1.8)
- HashMap源码分析——JDK1.8
- JDK1.8的HashMap源码分析
- Java 8 分析源码之 HashMap
- Java源码分析之HashMap(JDK1.8)
- HashMap源码分析——JDK1.8
- DNS服务器配置和测试
- H5小白入门笔记(1)
- 网络爬虫程序(Java实现)
- Python中使用支持向量机(SVM)实践
- 1027. 打印沙漏(20)
- HashMap源码分析_JDK1.8版本
- UML之类图
- nested exception is org.springframework.core.NestedIOException: ASM ClassReader failed to parse cla
- BZOJ 4176 [莫比乌斯反演][杜教筛]
- NetBeans主题配色方案加设置
- 排序(冒泡,选择,插入)
- 学前班_学会看原理图-RAM-like
- Java对象的持久化概念
- 句柄 Handle 的通俗解析