JAVA数据结构——Map之HashMap
一、原型及简介
原型:public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
简介:HashMap基于散列表实现的一个key-value数据结构,能够实现通过key值快速查找。HashMap继承自AbstractMap抽闲类,实现了Map接口。
二、数据结构原理介绍
如下图所示,HashMap是利用数组与链表结合的形式构建的。竖列为数组结构,默认初始数量为16(1<<4)个,横列为链表结构用于解决散列冲突的问题。当数组中有值得元素超过了装载因子的比例(默认为0.75)时,会引发扩容的操作。此操作是为了避免元素过满时引起的链表长度过长,从而影响查找性能。
上图为jdk1.7之前的实现,jdk1.8实现方法是当某一个桶中的元素个数超过了8时,将此桶中的链表构建成红黑树。
三、常用源码解析
1、常量说明
1 /** 2 * 默认初始容量 3 */ 4 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 5 6 /** 7 * 最大元素数量 8 */ 9 static final int MAXIMUM_CAPACITY = 1 << 30; 10 11 /** 12 * 默认装载因子 13 */ 14 static final float DEFAULT_LOAD_FACTOR = 0.75f; 15 16 /** 17 * 当一个桶中的元素的数量大于8时,该链表结构可能被转化成一棵红黑树,优化查找 18 */ 19 static final int TREEIFY_THRESHOLD = 8; 20 21 /** 22 * 当一个桶中的元素的数量小于6时,该树结构被转化成链表。 23 */ 24 static final int UNTREEIFY_THRESHOLD = 6; 25 26 /** 27 * 桶被树化的另一个条件是,当hashmap中元素个数大于4 * MIN_TREEIFY_CAPACITY 。避免调整大小和treei阈值之间的冲突。 28 */ 29 static final int MIN_TREEIFY_CAPACITY = 64;
常量值说明
2、变量说明
1 /** 2 * The table, initialized on first use, and resized as 3 * necessary. When allocated, length is always a power of two. 4 * (We also tolerate length zero in some operations to allow 5 * bootstrapping mechanics that are currently not needed.) 6 */ 7 transient Node<K,V>[] table; 8 9 /** 10 * Holds cached entrySet(). Note that AbstractMap fields are used 11 * for keySet() and values(). 12 */ 13 transient Set<Map.Entry<K,V>> entrySet; 14 15 /** 16 * HashMap中当前元素个数 17 */ 18 transient int size; 19 20 /** 21 * HashMap对象被修改次数,防止出现多个线程修改出现的线程不一致性,每次修改HashMap的值时,都会自增。当使用Iterator操作HashMap时,会用此值与Iterator内部的值做一次比较,从而判断HashMap有没有被其他线程修改。故建议每次遍历HashMap时都使用Iterator。 22 */ 23 transient int modCount; 24 25 /** 26 * 装载因子 27 */ 28 final float loadFactor;
变量值说明
漏了一个变量:threshold,代表着扩容的阈值,其值为 当前容量*装载因子
3、节点数据结构
1 static class Node<K,V> implements Map.Entry<K,V> { 2 final int hash; //散列码 3 final K key; //key值 4 V value; //value值 5 Node<K,V> next; //链表结构指针,指向下一节点 6 7 Node(int hash, K key, V value, Node<K,V> next) { 8 this.hash = hash; 9 this.key = key; 10 this.value = value; 11 this.next = next; 12 } 13 14 public final K getKey() { return key; } 15 public final V getValue() { return value; } 16 public final String toString() { return key + "=" + value; } 17 18 // 返回该节点的散列码 19 public final int hashCode() { 20 // key值的散列码 幂运算 value值得散列码 21 // 散列函数为空值返回0,非空值则返回该对象的32位JVM地址 22 return Objects.hashCode(key) ^ Objects.hashCode(value); 23 } 24 25 public final V setValue(V newValue) { 26 V oldValue = value; 27 value = newValue; 28 return oldValue; 29 } 30 31 public final boolean equals(Object o) { 32 if (o == this) 33 return true; 34 if (o instanceof Map.Entry) { 35 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 36 if (Objects.equals(key, e.getKey()) && 37 Objects.equals(value, e.getValue())) 38 return true; 39 } 40 return false; 41 } 42 }
节点数据结构
4、常用方法
1 /** 2 * 指定初始大小以及装载因子 3 */ 4 public HashMap(int initialCapacity, float loadFactor) { 5 if (initialCapacity < 0) 6 throw new IllegalArgumentException("Illegal initial capacity: " + 7 initialCapacity); 8 if (initialCapacity > MAXIMUM_CAPACITY) 9 initialCapacity = MAXIMUM_CAPACITY; 10 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 11 throw new IllegalArgumentException("Illegal load factor: " + 12 loadFactor); 13 this.loadFactor = loadFactor; 14 this.threshold = tableSizeFor(initialCapacity); 15 } 16 17 /** 18 * 返回一个比cap大的最小的2的幂次方整数 19 */ 20 static final int tableSizeFor(int cap) { 21 int n = cap - 1; 22 n |= n >>> 1; 23 n |= n >>> 2; 24 n |= n >>> 4; 25 n |= n >>> 8; 26 n |= n >>> 16; 27 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 28 }
构造方法
因为HashMap的容量必须是2的幂次方,所以构造方法中有关于tableSizeFor方法,获得比给定容量大的最小的2的幂次方整数,很霸气的算法,其具体的说明可参考链接:
【转载】http://blog.csdn.net/fan2012huan/article/details/51097331(写的很详细,很好)。
1 /** 2 * 将key-value键值对放入HashMap中 3 */ 4 public V put(K key, V value) { 5 return putVal(hash(key), key, value, false, true); 6 } 7 8 /** 9 * 实际put的方法 10 */ 11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 12 boolean evict) { 13 Node<K,V>[] tab; //暂存HashMap节点数组 14 Node<K,V> p; //暂存本次要插入的节点元素数据 15 int n, i; 16 17 //如果当前HashMap为空,则计算新分配空间 18 if ((tab = table) == null || (n = tab.length) == 0) 19 n = (tab = resize()).length; 20 // 如果计算出的新节点位置(hash & (n - 1) 等价于 hash % n)是空,则将元素直接放入 21 if ((p = tab[i = (n - 1) & hash]) == null) 22 tab[i] = newNode(hash, key, value, null); 23 //插入新的节点,并重新组织HashMap(确定位置并决定是否树化) 24 else { 25 Node<K,V> e; K k; 26 // 插入了重复的key值(hash码一致且key值一致) 27 if (p.hash == hash && 28 ((k = p.key) == key || (key != null && key.equals(k)))) 29 e = p; 30 // 如果p是红黑树,则执行红黑树的插入操作 31 else if (p instanceof TreeNode) 32 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 33 // 此分支代表了链表的插入操作 34 else { 35 for (int binCount = 0; ; ++binCount) { 36 // 到达链表尾端,则执行插入 37 if ((e = p.next) == null) { 38 //插入 39 p.next = newNode(hash, key, value, null); 40 如果节点数量超过阈值,则执行树化操作 41 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 42 treeifyBin(tab, hash); 43 break; 44 } 45 // 插入了重复值 46 if (e.hash == hash && 47 ((k = e.key) == key || (key != null && key.equals(k)))) 48 break; 49 p = e; 50 } 51 } 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 ++modCount; 61 //如果元素个数超过阈值,则重新分配空间,并组织数据结构 62 if (++size > threshold) 63 resize(); 64 afterNodeInsertion(evict); 65 return null; 66 } 67 68 /** 69 * 针对每个桶重新分配空间 70 */ 71 final Node<K,V>[] resize() { 72 Node<K,V>[] oldTab = table; //暂存当前table结构 73 int oldCap = (oldTab == null) ? 0 : oldTab.length; //暂存当前桶的数量 74 int oldThr = threshold; //暂存扩容的阈值 75 int newCap, newThr = 0; //定义新的容量和阈值 76 // 如果原有HashMap不为空 77 if (oldCap > 0) { 78 //如果容量已经达到了上限,则不扩容,返回原oldTab 79 if (oldCap >= MAXIMUM_CAPACITY) { 80 threshold = Integer.MAX_VALUE; 81 return oldTab; 82 } 83 //如果容量没有达到上限,则将容量及扩容阈值均翻倍 84 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 85 oldCap >= DEFAULT_INITIAL_CAPACITY) 86 newThr = oldThr << 1; // double threshold 87 } 88 // 容量为0但老的阈值大于0,则阈值保持不变 89 else if (oldThr > 0) // initial capacity was placed in threshold 90 newCap = oldThr; 91 // 如果容量与阈值均为0,则执行初始化 92 else { // zero initial threshold signifies using defaults 93 newCap = DEFAULT_INITIAL_CAPACITY;//容量为默认容量 94 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//阈值为默认容量*默认阀值(16*0.75) 95 } 96 // 如果现有容量翻倍后大于最大容量或现有容量小于系统默认值(16),才会出现新阈值=0的情况, 97 if (newThr == 0) { 98 float ft = (float)newCap * loadFactor; 99 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 100 (int)ft : Integer.MAX_VALUE); 101 } 102 103 // 设置最新的扩容阈值 104 threshold = newThr; 105 106 // 创建扩容后的桶数组 107 @SuppressWarnings({"rawtypes","unchecked"}) 108 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 109 table = newTab; 110 111 //重新组织每个桶内的链表或树状结构 112 if (oldTab != null) { 113 //遍历每个桶,分别处理每个桶中的数据 114 for (int j = 0; j < oldCap; ++j) { 115 Node<K,V> e; 116 117 //当前桶不为空,则需将oldTab中的内容组织到newTab中 118 if ((e = oldTab[j]) != null) { 119 oldTab[j] = null; 120 if (e.next == null) //e没有子节点,则根据e的hash值直接将此节点放到扩容后的桶数组中合适位置 121 // 此处e.hash & (newCap - 1)等价于e.hash % newCap 122 newTab[e.hash & (newCap - 1)] = e; 123 else if (e instanceof TreeNode) //如果e是个树型节点,则遍历红黑树,将树中的每个节点放到新的桶数组中合适的位置,并根据新的结构决定是否需要将每个桶做树化 124 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 125 else { // e是个链式节点 126 Node<K,V> loHead = null, loTail = null; 127 Node<K,V> hiHead = null, hiTail = null; 128 Node<K,V> next; 129 do { 130 next = e.next; 131 // 因为容量扩大了二倍,则元素要么保持不变,要么放到index + oldCap位置 132 if ((e.hash & oldCap) == 0) {// 元素位置保持不变,先将元素放到lo链表中 133 if (loTail == null) 134 loHead = e; 135 else 136 loTail.next = e; 137 loTail = e; 138 } 139 else {// 元素位置需要移动,先将元素放到hi链表中 140 if (hiTail == null) 141 hiHead = e; 142 else 143 hiTail.next = e; 144 hiTail = e; 145 } 146 } while ((e = next) != null); 147 if (loTail != null) {//将lo链表放到newTab中原来(j)的位置 148 loTail.next = null; 149 newTab[j] = loHead; 150 } 151 if (hiTail != null) {//将hi链表放到newTab中扩容(j+oldCap)的位置 152 hiTail.next = null; 153 newTab[j + oldCap] = hiHead; 154 } 155 } 156 } 157 } 158 } 159 160 //返回最新的结构 161 return newTab; 162 }
public V put(K key, V value)
put方法包含了HashMap的实际初始化及构建的过程,仔细研究put方法,可以更好的了解HashMap这种数据结构
1 /** 2 * 根据key值获取value值 3 */ 4 public V get(Object key) { 5 Node<K,V> e; 6 return (e = getNode(hash(key), key)) == null ? null : e.value; 7 } 8 9 /** 10 * 根据哈希码及key值获取value值 11 */ 12 final Node<K,V> getNode(int hash, Object key) { 13 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 14 15 //如果表不为空且表的长度不为空且根据hash码定位到桶不为空 16 if ((tab = table) != null && (n = tab.length) > 0 && 17 (first = tab[(n - 1) & hash]) != null) { 18 19 //如果该桶的第一个元素hash码与传参相同且key值也相同,则返回该元素节点 20 if (first.hash == hash && // always check first node 21 ((k = first.key) == key || (key != null && key.equals(k)))) 22 return first; 23 24 //如果该节点的下一个下一个节点不为空 25 if ((e = first.next) != null) { 26 //如果该节点是树形节点,则遍历红黑树查找匹配节点 27 if (first instanceof TreeNode) 28 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 29 30 //如果是链式节点,则遍历该链表查找匹配节点 31 do { 32 if (e.hash == hash && 33 ((k = e.key) == key || (key != null && key.equals(k)))) 34 return e; 35 } while ((e = e.next) != null); 36 } 37 } 38 return null; 39 }
public V get(Object key)