转载请注明出处 http://www.cnblogs.com/justenjoy/p/8920702.html
本文主要结合自己的实际应用和源码的解析,来分析HashMap的实现原理。
主要分析内容:
HashMap的散列分布逻辑,hash碰撞的处理,扩容的触发,树化等内容。
行文顺序:
1.结合源码,对基本的属性,内容分析
2.结合实例对照源码
下面首先讲一下基本的属性和原理:
//默认容量 16 //当首次put()操作时,会调用resize(),初始化table数组,(table)数组 //下面会有介绍。数组的长度就是这个默认参数 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; //这是一个系数,只要知道触发扩容的条件是: //使用容量 > 当前总容量 * DEFAULT_LOAD_FACTOR static final float DEFAULT_LOAD_FACTOR = 0.75f; //触发树化的条件,起初当存在相同hash阵列到同一个table数组的位置i上 //那么在这个位置上以链表的形式储存,当大于这个长度,则转成树 static final int TREEIFY_THRESHOLD = 8; //这既是HashMap的真身,其实就是一个数组,元素是Node类。 //Node是一个内部类,下面会有介绍 transient Node<K,V>[] table; //Node<K,V>类 //这个类其实就是HashMap元素储存的基本形式 //而且Node可以形成链表,以链表的形式存储在table的第i个位置 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; } }
然后我们再来看一下put()操作的逻辑
当put()操作时要处理hash碰撞,扩容,树化等可能出现的情景。也是本文分析的核心。
//put 操作 public V put(K key, V value) { //调用putValue 方法 return putVal(hash(key), key, value, false, true); } //hash方法,可以忽略 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } //核心方法 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //一般来说,首次put会进入这个if条件,然后调用resize方法 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 根据hash 散列到table数组上,如果发现数组的该位置上并没有 // 填充对象,则直接新建Node,填充 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { // 这里就要处理,当hash值相同,散列到table数组的同一位置时的 // 处理逻辑。 Node<K,V> e; K k; //好吧,就算你hash值一样,也不一定要你插在我的后面 // 除非既不 == 也不 equals。不然只能把当前节点替换掉 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果在table的第i位置出,延伸出去的链表已经转成树了。 // 则执行树的插入操作 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 然后,这一块主要是往table的第i位置,延伸出的链表最后 // 插入新的节点的。通过for循环,达到链表末尾 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; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 如果以储存的容量 > threshold 的话,则触发扩容 // threshold = 当前容量 * DEFAULT_LOAD_FACTOR (上面参数介绍过) if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
上面一些基本的属相和方法已经介绍了。
然后介绍一下HashMap的储存结构:
首先上面已经介绍了Node<K,V>类是HashMap储存的基本结构类型。
HashMap其实就是一个Node类型的数组。
储存过程:
1.先分配容量
2.根据hash值阵列
2.1.当hash阵列计算到数组的位置 n 时,如果 table[n]初未储存对象( table[n] == null),则直接new Node<K,V>储存
2.2.如果 table[n]储存对象(table[n] != null)。这种情况下要根据 K 的 hashCode 和equals方法,判断是更新当前节点,还是在最后插入。
如果插入的情况,判断是否达到树化阀值。如果达到则树化。如果未达到,则在链表最后插入。
3.每次插入后判断容量,是否进行扩容
然后具体的简单实例:
通过重写hashCode 和 equals 方法,来产生hash碰撞的情况,然后触发树化
package temp; import java.util.HashMap; import java.util.Map; public class HashMapDemo { public static void main(String[] args) { Map<MyValue,Object> hashMap = new HashMap<MyValue, Object>(); hashMap.put(new MyValue("one"),"first"); hashMap.put(new MyValue("two"),"secend"); for(int i=0;i<18;i++){ hashMap.put(new MyValue("dump"),"dump"); } } private static class MyValue{ String value; public MyValue(String value){ this.value = value; } // 重写,hash散列到 table[n]上 public int hashCode(){ return value.hashCode(); } // 还记的putVal的判断方法么,只要散列到同一个位置 // 而且通过equals 返回true,才会去追加 public boolean equals(MyValue v){ return false; } public String toString(){ return value; } } }
这里只是介绍了一些基本的核心和原理。
当然还有扩容方法 resize(), 树化方法 treeifyBin(),移除节点方法 removeNode()
可以参照源码看一下具体的实现逻辑。
好了,就到这了。