首页 > 代码库 > HashMap详解
HashMap详解
HashMap详解
HashMap
注:由于HashMap的实现机制过于复杂,内部由哈希数组+链表+红黑树实现,并且其中涉及到Hash算法、哈希数组扩容机制、负载因子、红黑树等等一系列较为复杂的问题,具体可以去看美团网技术团队的关于HashMap的文章。在我自己实现的HashMap中,我把许多东西难度都降低了,我主要想把HashMap的实现机制说清楚,而具体怎么实现的,我自己许多东西不会,以后再来写清楚吧。
1 概述
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
- HashMap内部是哈希数组+链表+红黑树实现
- HashMap遍历无序,但其有很快的访问速度
- HashMap中key不能有重复元素,可以有一个null;value可以有重复元素多个null对象
- HashMap是线程不安全的
2 实现机制
2.1 原理
HashMap的实现如下图:
HashMap是使用哈希表存放元素,使用了链地址法(数组+链表)解决哈希冲突。JDK1.8后当链表长度大于8时使用了红黑树解决哈希冲突。
首先通过key的hashCode()方法找到key的哈希值,通过Hash算法算出该key存放的位置(哈希数组的下标),在这里可能发生哈希冲突,然后插入到链表或红黑树中存放。
HashMap解决哈希冲突主要有两点:1.好的Hash算法使key-value对均匀分布在哈希数组中。2.key-value对过多时,扩充哈希数组的容量。
2.2 具体实现
HashMap的几个字段
1 transient Node<K,V>[] table; // 哈希数组 2 transient int size; // HashMap存放元素个数 3 int threshold; // key-value对的最大容量(哈希数组长度Capacity*负载因子loadFactor) 4 final float loadFactor; // 负载因子
(1).Node[] table是哈希桶数组。Node实现了Map.Entry接口(Map.Entry就是键值对)的链表。
1 static class Node implements Map.Entry { 2 final int hash; // 经过hash运算后得出的数组下标 3 final K key; 4 V value; 5 Node next; 6 Node(int hash, K key, V value, Node next) { 7 this.hash = hash; 8 this.key = key; 9 this.value =http://www.mamicode.com/ value; 10 this.next = next; 11 } 12 public final K getKey() { return key; } 13 public final V getValue() { return value; } 14 public final String toString() { return key + "=" + value; } 15 public final int hashCode() { 16 return Objects.hashCode(key) ^ Objects.hashCode(value); 17 } 18 public final V setValue(V newValue) { 19 V oldValue =http://www.mamicode.com/ value; 20 value =http://www.mamicode.com/ newValue; 21 return oldValue; 22 } 23 public final boolean equals(Object o) { 24 if (o == this) 25 return true; 26 if (o instanceof Map.Entry) { 27 Map.Entry e = (Map.Entry)o; 28 if (Objects.equals(key, e.getKey()) && 29 Objects.equals(value, e.getValue())) 30 return true; 31 } 32 return false;
(2).threshold表示该哈希数组存放的最大存放key-value对数,threshold = loadFactory(负载因子) * Capatity(哈希数组长度)。
loadFactoy默认0.75,Capatity默认16。在每次存放key-value对时检查threshold,若其不足则需要扩容(哈希数组长度)。
扩容方法,我不太懂就不写了,源码见HashMap中的resize()方法。
HashMap中put方法
注:美团上面的摘抄
1 public V put(K key, V value){ 2 // 对key的hashCode()做hash 3 return putVal(hash(key), key, value, false, true); 4 } 5 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){ 6 Node<K,V>[] tab; Node<K,V> p; int n, i; 7 // 步骤①:tab为空则创建 8 if ((tab = table) == null || (n = tab.length) == 0) 9 n = (tab = resize()).length; 10 // 步骤②:查看该链表或者红黑树中是否有元素 11 if ((p = tab[i = (n - 1) & hash]) == null) 12 // 没有元素就创建头元素 13 tab[i] = newNode(hash, key, value, null); 14 else { 15 Node<K,V> e; K k; 16 // 步骤③:判断table[i]中首个元素是否是该key 17 if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))){ 18 // 是-->替换 19 e = p; 20 } 21 // 步骤④:判断是否是红黑树 22 else if (p instanceof TreeNode){ 23 // 是-->直接在树中插入键值对 24 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 25 } 26 else { 27 // 遍历链表 28 for (int binCount = 0; ; ++binCount) { 29 if ((e = p.next) == null) { 30 // 步骤⑤:判断链表长度是否大于8,若大于转换成红黑树处理 31 p.next = newNode(hash, key, value, null); 32 // 转换成红黑树处理 33 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 34 treeifyBin(tab, hash); 35 break; 36 } 37 if (e.hash == hash && 38 ((k = e.key) == key || (key != null && key.equals(k)))) 39 break; 40 p = e; 41 } 42 } 43 if (e != null) { // existing mapping for key 44 V oldValue =http://www.mamicode.com/ e.value; 45 if (!onlyIfAbsent || oldValue =http://www.mamicode.com/= null) 46 e.value =http://www.mamicode.com/ value; 47 afterNodeAccess(e); 48 return oldValue; 49 } 50 } 51 ++modCount; 52 // 步骤⑥:插入成功,判断是否超过最大容量 53 if (++size > threshold){ 54 // 扩容 55 resize(); 56 } 57 afterNodeInsertion(evict); 58 return null; 59 }
(3).扩容机制:不会以后来写。
3.自己写的HashMap
1 class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>{ 2 private static final int DEFAULT_CAPACITY = 16; // 哈希数组默认大小 3 private int size; // HashMap存放元素个数 4 private Node[] table; // 哈希数组 5 6 HashMap(){ 7 table = new Node[DEFAULT_CAPACITY]; 8 } 9 10 @Override 11 public V put(K key, V value) { 12 // 找到该key对应的哈希数组下标 13 int index = hash(key); 14 // 该链表没有元素 15 if(table[index] == null){ 16 table[index] = new Node<>(index, key, value, null); 17 size++; 18 return null; 19 } 20 @SuppressWarnings("unchecked") 21 Node<K, V> node = table[index]; 22 Node<K, V> prev = node; 23 // 遍历该链表 24 while(node != null){ 25 // 找到key,则替换value并返回原value 26 if(Objects.equals(node.getKey(),key)){ 27 V oldValue =http://www.mamicode.com/ node.getValue(); 28 node.setValue(value); 29 return oldValue; 30 } 31 prev = node; 32 node = node.next; 33 } 34 // 未找到,则加入链表 35 prev.next = new Node<>(index, key, value, null); 36 size++; 37 return null; 38 } 39 40 @Override 41 public V get(Object key) { 42 // 找到该key对应的哈希数组下标 43 int index = hash(key); 44 // 该链表没有元素 45 if(table[index] == null){ 46 return null; 47 } 48 @SuppressWarnings("unchecked") 49 Node<K, V> node = table[index]; 50 // 遍历该链表 51 do { 52 if(Objects.equals(node.getKey(),key)){ 53 return node.getValue(); 54 } 55 node = node.next; 56 } while (node != null); 57 return null; 58 } 59 60 @Override 61 public V remove(Object key) { 62 // 找到该key对应的哈希数组下标 63 int index = hash(key); 64 @SuppressWarnings("unchecked") 65 Node<K, V> node = table[index]; 66 // 该链表没有元素 67 if(node == null){ 68 return null; 69 } 70 // 该链表只有一个元素 71 if(node.next == null){ 72 if(Objects.equals(node.getKey(),key)){ 73 V oldValue =http://www.mamicode.com/ node.getValue(); 74 table[index] = null; 75 size--; 76 return oldValue; 77 } 78 return null; 79 } 80 // 遍历该链表 81 Node<K, V> prev = node; 82 node = node.next; 83 while(node != null){ 84 if(Objects.equals(node.getKey(),key)){ 85 prev.next = node.next; 86 size--; 87 return node.getValue(); 88 } 89 node = node.next; 90 prev = prev.next; 91 } 92 return null; 93 } 94 95 @Override 96 @SuppressWarnings("unchecked") 97 public Set<Entry<K, V>> entrySet() { 98 Set<Entry<K, V>> set = new HashSet<>(); 99 for(Node<K, V> node: table ){ 100 while(node != null){ 101 set.add(node); 102 node = node.next; 103 } 104 } 105 return set; 106 } 107 108 @Override 109 public int size() { 110 return size; 111 } 112 113 /** 114 * 计算key的哈希值,然后Hash算法(取模)求出该key对应的数组下标 115 */ 116 public int hash(Object key){ 117 return Objects.hashCode(key) % table.length; 118 } 119 120 static class Node<K, V> implements Map.Entry<K, V>{ 121 final int hash; // 经过hash运算得出的数组下标 122 final K key; 123 V value; 124 Node<K, V> next; 125 126 Node(int hash, K key, V value, Node<K, V> next) { 127 this.hash = hash; 128 this.key = key; 129 this.value =http://www.mamicode.com/ value; 130 this.next = next; 131 } 132 133 @Override 134 public K getKey() { 135 return this.key; 136 } 137 138 @Override 139 public V getValue() { 140 return this.value; 141 } 142 143 @Override 144 public V setValue(V value) { 145 V oldValue = http://www.mamicode.com/this.value; 146 this.value =http://www.mamicode.com/ value; 147 return oldValue; 148 } 149 150 @Override 151 public boolean equals(Object obj) { 152 if(obj == this){ 153 return true; 154 } 155 if(obj instanceof Map.Entry<?, ?>){ 156 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj; 157 return Objects.equals(entry.getKey(),this.key) && Objects.equals(entry.getValue(),this.value); 158 } 159 return false; 160 } 161 162 @Override 163 public int hashCode() { 164 return Objects.hashCode(this.key) ^ Objects.hashCode(this.value); 165 } 166 167 @Override 168 public String toString() { 169 return this.key + "=" + this.value; 170 } 171 } 172 }
4.Reference
美团点评技术团队:http://tech.meituan.com/java-hashmap.html
HashMap详解