static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
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) // entry数组为空,new HashMap的时候,并无初始化数组 n = (tab = resize()).length; //初始化数组,稍后看resize()实现 if ((p = tab[i = (n - 1) & hash]) == null) //分配到的数组位置为null,这里一个很巧妙的操做,没有用取模%运算,而是这个位与,效率更高并且正好这里等于取模 tab[i] = newNode(hash, key, value, null); else {//分配到的位置不是null的状况,要遍历链表 Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // key跟链表第一个元素的key相同,这里比较的是hashcode跟equals,从本方法以及Node的构造方法来看,Node的hash就是其中键的hash值 e = p; // 注意,这里找到后并无修改节点的值,节点的值是在后边修改的 else if (p instanceof TreeNode) //是treenode,jdk8在链表长度超过8后将node改成红黑树进行优化 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); // 结合Node的构造方法,node的hash就是此处的hash 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)))) //链表有节点的hash跟新元素相同而且equals判断也相等 break; //找到了,终止循环 p = e; // 注意,这句不在if范围内,仅仅在for循环内而已,因此若是已经有对应节点已经有oldValue的状况下,并无修改原来的值,修改的操做在后边 } } if (e != null) { // existing mapping for key //有oldValue的状况,e就是旧节点 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) // 容许重写原来值,或者原来的值为null,注意,onlyIfAbsent,这个属性hashmap实际没用,一直是默认false,也就一直默认覆盖原来的值 e.value = value; afterNodeAccess(e); // 值更新以后的操做,hashmap中此方法为空,无任何操做,该方法以及后边的afterNodeInsertion()都是给LinkedHashMap用的。 return oldValue; } } ++modCount; // 修改计数器,其实只有新增才修改了此值,覆盖原来值的操做,由于提早return 了,此值没有改变 if (++size > threshold) // hashMpa的容量达到了阈值 resize(); afterNodeInsertion(evict); return null; }
// Callbacks to allow LinkedHashMap post-actions void afterNodeAccess(Node<K,V> p) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(Node<K,V> p) { }
链表的实现类Node:html
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) { // 节点的hash就是所给元素的hash 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) // 地址相等----------其实这个值不必定是内存地址,不一样虚拟机实现不一样,sun貌似是地址的一个hash值 return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) // 键跟值的equals都相等 return true; } return false; } }
很是重要的resize() 方法:java
/** * Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * @return the table */
1. final Node<K,V>[] resize() { 2. Node<K,V>[] oldTab = table; //老的数组,也就是所谓的桶 3. int oldCap = (oldTab == null) ? 0 : oldTab.length; // 老的容量 4. int oldThr = threshold; // 阈值 5. int newCap, newThr = 0; 6. if (oldCap > 0) { // 老的容量>0,说明map里已经有元素了 7. if (oldCap >= MAXIMUM_CAPACITY) { // 老的容量已经很大了,直接把阈值扩展到最大,再也不容量翻倍了,太费劲了,并且效率提高有限,此时容量2的29次方,大概在3亿 8. threshold = Integer.MAX_VALUE; 9. return oldTab; 10. } 11. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //老容量在2<<<28的时候还能翻倍,到了2<<<29就不翻倍了,凑合着用吧 12. newThr = oldThr << 1; // double threshold 13. } 14. else if (oldThr > 0) // initial capacity was placed in threshold // 老容量==0,阈值>0,说明调用了new HashMap(容量=0,加载因子); 15. newCap = oldThr; 16. else { // zero initial threshold signifies using defaults //都是用默认值 17. newCap = DEFAULT_INITIAL_CAPACITY; // 默认1<<<4 = 16 18. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //默认16*0.75=12 19. } 20. if (newThr == 0) { //上来就new HashMap(容量大于2<<<29);没有指定加载因子的状况,本身计算阈值 21. float ft = (float)newCap * loadFactor; 22. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 23. (int)ft : Integer.MAX_VALUE); 24. } 25. threshold = newThr; 26. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 27. table = newTab; 28. if (oldTab != null) { 29. for (int j = 0; j < oldCap; ++j) { // 重点来了,迁移老数组进新数组 30. Node<K,V> e; 31. if ((e = oldTab[j]) != null) { 32. oldTab[j] = null; 33. if (e.next == null) //原数组j下标下,就一个元素 34. newTab[e.hash & (newCap - 1)] = e; //直接快速位与取模,找下标 35. else if (e instanceof TreeNode) //是红黑树节点 36. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 37. else { // preserve order 38. Node<K,V> loHead = null, loTail = null; //这里分别lowHead tail跟highHead tail两个对象,稍后分析为啥这么写 39. Node<K,V> hiHead = null, hiTail = null; 40. Node<K,V> next; 41. do { // j下标下有好多个链表的状况 42. next = e.next; 43. if ((e.hash & oldCap) == 0) {//hash中参与计算的新一位为0,下标不变; 计算方式参照上方图解; 44. if (loTail == null) 45. loHead = e; 46. else 47. loTail.next = e; 48. loTail = e; 49. } 50. else { // hash中参与计算的新一位为1,下标变为 原容量+原下标,不用从新计算了,省事儿,就是优化在这里了(实际这一步也是计算了,只是计算比原来简单了) 51. if (hiTail == null) 52. hiHead = e; 53. else 54. hiTail.next = e; 55. hiTail = e; 56. } 57. } while ((e = next) != null);// 遍历下一个元素 58. if (loTail != null) { // 设置下标,放入新数组 59. loTail.next = null; 60. newTab[j] = loHead; 61. } 62. if (hiTail != null) { // 设置下标,放入新数组 63. hiTail.next = null; 64. newTab[j + oldCap] = hiHead; 65. } 66. } 67. } 68. } 69. } 70. return newTab; 71. }
关于HashMap的初始容量:node
1. public HashMap(int initialCapacity, float loadFactor) { 2. if (initialCapacity < 0) //给定初始长度<0则抛异常 3. throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); 4. if (initialCapacity > MAXIMUM_CAPACITY) //给定初始长度太大,则设为默认最大容量 5. initialCapacity = MAXIMUM_CAPACITY; 6. if (loadFactor <= 0 || Float.isNaN(loadFactor)) //给定负载因子<0或者不是数字,抛异常 7. throw new IllegalArgumentException("Illegal load factor: " + 8. loadFactor); 9. this.loadFactor = loadFactor; //负载因子采用给定值 10. this.threshold = tableSizeFor(initialCapacity); //阈值利用初始容量,经过一个方法进行计算 11. }
计算方法:算法
1. static final int tableSizeFor(int cap) { 2. int n = cap - 1; 3. n |= n >>> 1; // n = n | n >>> 1 4. n |= n >>> 2; 5. n |= n >>> 4; 6. n |= n >>> 8; 7. n |= n >>> 16; 8. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 9. }
这个方法的做用就是不管初始容量是多少,最终的容量都将计算为不小于初始容量的2的最小次幂;好比初始容量为4,则经变化后初始容量仍为4,若初始容量为5,则变化后容量应该为8而不是5;之因此这么变化,是由于resize的机制中计算方式,table的长度必须为2的幂,不然计算方式会出错,并且计算起来会比如今复杂。数组
Iterator<Map.Entry<String, String>> iterator; iterator = hashMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> next = iterator.next(); String key = next.getKey(); String value = next.getValue(); }
咱们经过map获得了一个entrySet对象,而后又获取了一个Iterator对象,最终就是经过这个Iterator来遍历的,那么这个Iterator是怎么个结构,如何实现遍历的呢?数据结构
final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } //...... }
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } }
HashIterator的代码:app
abstract class HashIterator { Node<K,V> next; // next entry to return Node<K,V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { //构造方法 expectedModCount = modCount; Node<K,V>[] t = table; //map中哈希表的数组 current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null);//找到数组中第一个非空元素,赋值给next } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) {//本链表找完了,顺着数组找下一个链表,而后遍历 do {} while (index < t.length && (next = t[index++]) == null); } return e; } }
总结,遍历的过程就是找到数组中第一链表开始的位置,顺着遍历完本链表,而后顺着数组找第二个链表的位置,而后遍历第二个链表,这样一条链表一条链表的顺着来的。post