【Java深入研究】9、HashMap源码解析(jdk 1.8)
2018-06-18 02:38:48来源:未知 阅读 ()
一、HashMap概述
HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现。与HashTable主要区别为不支持同步和允许null作为key和value。由于HashMap不是线程安全的,如果想要线程安全,可以使用ConcurrentHashMap代替。
二、HashMap数据结构
HashMap的底层是哈希数组,数组元素为Entry。HashMap通过key的hashCode来计算hash值,当hashCode相同时,通过“拉链法”解决冲突
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。原本Map.Entry接口的实现类Entry改名为了Node。转化为红黑树时改用另一种实现TreeNode。
Node类
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; } }
TreeNode类
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } }
HashMap就是这样一个Entry(包括Node和TreeNode)数组,Node对象中包含键、值和hash值,next指向下一个Entry,用来处理哈希冲突。TreeNode对象包含指向父节点、子节点和前一个节点(移除对象时使用)的指针,以及表示红黑节点的boolean标识。
三、HashMap源码分析
1. 主要属性
transient Node<K,V>[] table; // 哈希数组 transient Set<Map.Entry<K,V>> entrySet; // entry缓存Set transient int size; // 元素个数 transient int modCount; // 修改次数 int threshold; // 阈值,等于加载因子*容量,当实际大小超过阈值则进行扩容 final float loadFactor; // 加载因子,默认值为0.75
2. 构造方法
以下是HashMap的几个构造方法。
/** * 根据初始化容量和加载因子构建一个空的HashMap. */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } /** * 使用初始化容量和默认加载因子(0.75). */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 使用默认初始化大小(16)和默认加载因子(0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * 用已有的Map构造一个新的HashMap. */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
3. 数据存取
putAll方法
public void putAll(Map<? extends K, ? extends V> m) { putMapEntries(m, true); } /** * Implements Map.putAll and Map constructor * * @param m the map * @param evict false when initially constructing this map, else * true (relayed to method afterNodeInsertion). */ final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) resize(); for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); // put核心方法 } } }
put方法
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) // table为空或length为0 n = (tab = resize()).length; // 初始化 if ((p = tab[i = (n - 1) & hash]) == null) // 如果hash所在位置为null,直接put tab[i] = newNode(hash, key, value, null); else { // tab[i]有元素,遍历节点后添加 Node<K,V> e; K k; // 如果hash、key都相等,直接覆盖 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); // 链表节点大于阈值8,调用treeifyBin方法,当tab.length大于64将链表改为红黑树 // 如果tab.length < 64或tab为null,则调用resize方法重构链表. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // hash、key都相等,此时节点即要更新节点 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 当前节点e = p.next不为null,表示链表中原本存在相同的key,则返回oldValue if (e != null) { // existing mapping for key V oldValue = e.value; // onlyIfAbsent值为false,参数主要决定存在相同key时是否执行替换 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) // 检查是否超过阈值 resize(); afterNodeInsertion(evict); return null; // 原HashMap中不存在相同的key,插入键值对后返回null }
treeifyBin方法源码如下:
1 final void treeifyBin(Node<K,V>[] tab, int hash) { 2 int n, index; Node<K,V> e; 3 //当tab为null或tab.length<64需要进行扩容 4 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) 5 resize(); 6 else if ((e = tab[index = (n - 1) & hash]) != null) { 7 TreeNode<K,V> hd = null, tl = null; 8 do { 9 TreeNode<K,V> p = replacementTreeNode(e, null); 10 if (tl == null) 11 hd = p; 12 else { 13 p.prev = tl; 14 tl.next = p; 15 } 16 tl = p; 17 } while ((e = e.next) != null); 18 if ((tab[index] = hd) != null) 19 //存储在红黑树 20 hd.treeify(tab); 21 } 22 }
现在我们来看一下为什么需要扩容:
1 /** 2 * The bin count threshold for using a tree rather than list for a 3 * bin. Bins are converted to trees when adding an element to a 4 * bin with at least this many nodes. The value must be greater 5 * than 2 and should be at least 8 to mesh with assumptions in 6 * tree removal about conversion back to plain bins upon 7 * shrinkage. 8 */ 9 static final int TREEIFY_THRESHOLD = 8; 10 11 /** 12 * The smallest table capacity for which bins may be treeified. 13 * (Otherwise the table is resized if too many nodes in a bin.) 14 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts 15 * between resizing and treeification thresholds. 16 */ 17 static final int MIN_TREEIFY_CAPACITY = 64;
MIN_TREEIFY_CAPACITY=64 > 4 * TREEIFY_THRESHOLD=4*8=32
如上图:转化红黑树的table容量最小容量64(JDK8定义的是64),至少是4*TREEIFY_THRESHOLD(单链表节点个数阀值,用以判断是否需要树形化)=32。用以避免 在扩容和树形结构化的阀值 可能产生的冲突。所以如果小于64必须要扩容。
三、get查找
可见外部查找和JDK7差别不大,只是原本是entry[]查询,JDK8编程Node[]查询.都是 hash(key)找到table中元素位置,再遍历找到链表(或者是红黑树)元素的key。根据元素类型判断到底是查询哪个类型
1 public V get(Object key) { 2 Node<K,V> e; 3 return (e = getNode(hash(key), key)) == null ? null : e.value; 4 } 5 6 /** 7 * Implements Map.get and related methods 8 * 9 * @param hash hash for key 10 * @param key the key 11 * @return the node, or null if none 12 */ 13 final Node<K,V> getNode(int hash, Object key) { 14 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 15 if ((tab = table) != null && (n = tab.length) > 0 && 16 (first = tab[(n - 1) & hash]) != null) { 17 if (first.hash == hash && // always check first node 18 ((k = first.key) == key || (key != null && key.equals(k)))) 19 return first; 20 if ((e = first.next) != null) { 21 if (first instanceof TreeNode) 22 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 23 do { 24 if (e.hash == hash && 25 ((k = e.key) == key || (key != null && key.equals(k)))) 26 return e; 27 } while ((e = e.next) != null); 28 } 29 } 30 return null; 31 }
四、resize扩容
第二部put的时候,有可能需要resize。 因为newCap是oldCap的两倍所以原节点的索引值要么和原来一样,要么就是原(索引+oldCap)和JDK 1.7中实现不同这里不存在rehash
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 Node<K,V>[] oldTab = table; 12 int oldCap = (oldTab == null) ? 0 : oldTab.length; 13 int oldThr = threshold; 14 int newCap, newThr = 0; 15 if (oldCap > 0) { 16 if (oldCap >= MAXIMUM_CAPACITY) { 17 threshold = Integer.MAX_VALUE; 18 return oldTab; 19 } 20 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 21 oldCap >= DEFAULT_INITIAL_CAPACITY) 22 newThr = oldThr << 1; // double threshold 23 } 24 else if (oldThr > 0) // initial capacity was placed in threshold 25 newCap = oldThr; 26 else { // zero initial threshold signifies using defaults 27 newCap = DEFAULT_INITIAL_CAPACITY; 28 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 29 } 30 if (newThr == 0) { 31 float ft = (float)newCap * loadFactor; 32 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 33 (int)ft : Integer.MAX_VALUE); 34 } 35 threshold = newThr; 36 @SuppressWarnings({"rawtypes","unchecked"}) 37 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 38 table = newTab; 39 if (oldTab != null) { 40 for (int j = 0; j < oldCap; ++j) { 41 Node<K,V> e; 42 if ((e = oldTab[j]) != null) { 43 oldTab[j] = null; 44 if (e.next == null) 45 newTab[e.hash & (newCap - 1)] = e; 46 else if (e instanceof TreeNode) 47 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 48 else { // preserve order 49 Node<K,V> loHead = null, loTail = null; 50 Node<K,V> hiHead = null, hiTail = null; 51 Node<K,V> next; 52 do { 53 next = e.next; 54 if ((e.hash & oldCap) == 0) { 55 if (loTail == null) 56 loHead = e; 57 else 58 loTail.next = e; 59 loTail = e; 60 } 61 else { 62 if (hiTail == null) 63 hiHead = e; 64 else 65 hiTail.next = e; 66 hiTail = e; 67 } 68 } while ((e = next) != null); 69 if (loTail != null) { 70 loTail.next = null; 71 newTab[j] = loHead; 72 } 73 if (hiTail != null) { 74 hiTail.next = null; 75 //把节点移动新的位置j+oldCap,这种情况不适用与链表的节点数大于8的情况 76 //链表节点大于8的情况会转换为红黑树存储 77 newTab[j + oldCap] = hiHead; 78 } 79 } 80 } 81 } 82 } 83 return newTab; 84 }
五.HashMap节点红黑树存储
好了终于到treeify了,大部分内容都在注解中
1 final void treeify(Node<K,V>[] tab) { 2 TreeNode<K,V> root = null; 3 for (TreeNode<K,V> x = this, next; x != null; x = next) { 4 next = (TreeNode<K,V>)x.next; 5 x.left = x.right = null; 6 if (root == null) { 7 x.parent = null; 8 x.red = false; 9 root = x; 10 } 11 else { 12 K k = x.key; 13 int h = x.hash; 14 Class<?> kc = null; 15 //遍历root,把节点x插入到红黑树中,执行先插入,然后进行红黑树修正 16 for (TreeNode<K,V> p = root;;) { 17 int dir, ph; 18 K pk = p.key; 19 if ((ph = p.hash) > h) 20 dir = -1; 21 else if (ph < h) 22 dir = 1; 23 else if ((kc == null && 24 (kc = comparableClassFor(k)) == null) || 25 (dir = compareComparables(kc, k, pk)) == 0) 26 dir = tieBreakOrder(k, pk);//比较k和pk的值,用于判断是遍历左子树还是右子树 27 TreeNode<K,V> xp = p; 28 if ((p = (dir <= 0) ? p.left : p.right) == null) { 29 x.parent = xp; 30 if (dir <= 0) 31 xp.left = x; 32 else 33 xp.right = x; 34 //修正红黑树 35 root = balanceInsertion(root, x); 36 //退出循环 37 break; 38 } 39 } 40 } 41 } 42 moveRootToFront(tab, root); 43 }
上面主要做的是红黑树的insert,我们知道红黑树insert后是需要修复的,为了保持红黑树的平衡,我们来看下红黑树平衡的几条性质:
1.节点是红色或黑色。
2.根是黑色。
3.所有叶子都是黑色(叶子是NIL节点)。
4.每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
当insert一个节点之后为了达到平衡,我们可能需要对节点进行旋转和颜色翻转(上面的balanceInsertion方法)。
1 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, 2 TreeNode<K,V> x) { 3 //插入的节点必须是红色的,除非是根节点 4 x.red = true; 5 //遍历到x节点为黑色,整个过程是一个上滤的过程 6 //xp=x.parent;xpp=xp.parent;xppl=xpp.left;xppr=xpp.right; 7 for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { 8 if ((xp = x.parent) == null) { 9 x.red = false; 10 return x; 11 } 12 //如果xp的黑色就直接完成,最简单的情况 13 else if (!xp.red || (xpp = xp.parent) == null) 14 return root; 15 //如果x的父节点是x父节点的左节点 16 if (xp == (xppl = xpp.left)) { 17 //x的父亲节点的兄弟是红色的(需要颜色翻转) 18 if ((xppr = xpp.right) != null && xppr.red) { 19 //x父亲节点的兄弟节点置成黑色 20 xppr.red = false; 21 //父几点和其兄弟节点一样是黑色 22 xp.red = false; 23 //祖父节点置成红色 24 xpp.red = true; 25 //然后上滤(就是不断的重复上面的操作) 26 x = xpp; 27 } 28 else { 29 //如果x是xp的右节点整个要进行两次旋转,先左旋转再右旋转 30 if (x == xp.right) { 31 root = rotateLeft(root, x = xp); 32 xpp = (xp = x.parent) == null ? null : xp.parent; 33 } 34 if (xp != null) { 35 xp.red = false; 36 if (xpp != null) { 37 xpp.red = true; 38 root = rotateRight(root, xpp); 39 } 40 } 41 } 42 } 43 //以左节点镜像对称就不做具体分析了 44 else { 45 if (xppl != null && xppl.red) { 46 xppl.red = false; 47 xp.red = false; 48 xpp.red = true; 49 x = xpp; 50 } 51 else { 52 if (x == xp.left) { 53 root = rotateRight(root, x = xp); 54 xpp = (xp = x.parent) == null ? null : xp.parent; 55 } 56 if (xp != null) { 57 xp.red = false; 58 if (xpp != null) { 59 xpp.red = true; 60 root = rotateLeft(root, xpp); 61 } 62 } 63 } 64 } 65 } 66 }
出处:
https://www.cnblogs.com/dennyzhangdd/p/6745282.html
http://blog.csdn.net/u014026363/article/details/56342142
http://www.cnblogs.com/skywang12345/p/3245399.html
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash