HashMap源码分析 一、HashMap的存储结构     HashMap是用哈希表实现的,解决冲突的方法是拉链法。具体说来,JDK1.7及以前HashMap是通过数组+链表的方式实现,每个数组单元指向一个链表,链表里存放一系列key的hashCode相同的数据。
    JDK1.8后采用数组+链表+红黑树实现,为了防止链表过长导致查询效率太低,当链表达到一定长度时(默认8),将链表转化为红黑树,使查询复杂度从O(n)降低到O(logn);红黑树的查询速率快但维护代价比较高,所以当红黑树节点数减少到一定值时(默认是6),将红黑树再转化为链表。
    以JDK1.8为例分析。
二、HashMap常用的字段属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static  final  int  DEFAULT_INITIAL_CAPACITY = 1  << 4 ; static  final  int  MAXIMUM_CAPACITY = 1  << 30 ;static  final  float  DEFAULT_LOAD_FACTOR = 0.75f ;static  final  int  TREEIFY_THRESHOLD = 8 ;static  final  int  UNTREEIFY_THRESHOLD = 6 ;static  final  int  MIN_TREEIFY_CAPACITY = 64 ;transient  Node<K,V>[] table;transient  Set<Map.Entry<K,V>> entrySet;transient  int  size;transient  int  modCount;int  threshold;final  float  loadFactor;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 ;         }     } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static  final  class  TreeNode <K ,V > extends  LinkedHashMap .Entry <K ,V >         TreeNode<K,V> parent;           TreeNode<K,V> left;         TreeNode<K,V> right;         TreeNode<K,V> prev;             boolean  red;         TreeNode(int  hash, K key, V val, Node<K,V> next) {             super (hash, key, val, next);         }                  final  TreeNode<K,V> root ()               for  (TreeNode<K,V> r = this , p;;) {                 if  ((p = r.parent) == null )                     return  r;                 r = p;             }         } 
三、HashMap的构造函数 1 2 3 4 public  HashMap ()          this .loadFactor = DEFAULT_LOAD_FACTOR;      } 
1 2 3 4 public  HashMap (int  initialCapacity)          this (initialCapacity, DEFAULT_LOAD_FACTOR);     } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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);     }   static  final  int  tableSizeFor (int  cap)          int  n = cap - 1 ;         n |= n >>> 1 ;         n |= n >>> 2 ;         n |= n >>> 4 ;         n |= n >>> 8 ;         n |= n >>> 16 ;         return  (n < 0 ) ? 1  : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 ;     } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public  HashMap (Map<? extends K, ? extends V> m)          this .loadFactor = DEFAULT_LOAD_FACTOR;         putMapEntries(m, false );     } final  void  putMapEntries (Map<? extends K, ? extends V> m, boolean  evict)                   int  s = m.size();         if  (s > 0 ) {             if  (table == null ) {                  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方法详解 1 2 3 4 5 6 7 public  V put (K key, V value)          return  putVal(hash(key), key, value, false , true );     } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 )             n = (tab = resize()).length;                           if  ((p = tab[i = (n - 1 ) & hash]) == null )             tab[i] = newNode(hash, key, value, null );         else  {                 Node<K,V> e; K k;                                       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 );                                                  if  (binCount >= TREEIFY_THRESHOLD - 1 )                              treeifyBin(tab, hash);                         break ;                     }                                          if  (e.hash == hash &&                         ((k = e.key) == key || (key != null  && key.equals(k))))                         break ;                     p = e;                 }             }                                                                 if  (e != null ) {                  V oldValue = e.value;                 if  (!onlyIfAbsent || oldValue == null )                     e.value = value;                 afterNodeAccess(e);                 return  oldValue;             }         }                  ++modCount;                  if  (++size > threshold)             resize();                  afterNodeInsertion(evict);         return  null ;     } 
四、hash的计算原理     这里,会先判断key是否为空,若为空则返回0。这也说明了hashMap是支持key传 null 的。若非空,则先计算key的hashCode值,赋值给h,然后把h右移16位,并与原来的h进行异或处理。这里实际上做了一个将低16位的值更新为低16位与高16位异或的结果,这样低16位会同时包含高16位的信息。
1 2 3 4 5 6 7 8 9 static  final  int  hash (Object key)          int  h;         return  (key == null ) ? 0  : (h = key.hashCode()) ^ (h >>> 16 );     } 
为什么将hashCode值要进行这样的操作呢? 
    答案是可以降低碰撞的概率。
    在putVal方法中,根据key的hash值寻找数组中的下标是通过(n-1) & hash得到的。这里的n代表数组的长度,保证是2的n次方。此时,hash % n与(n-1) & hash的结果相同。我们可以这样认为,hash值映射到数组中的下标是hash值中的低位决定的,这样映射时丢失了hash值中的高位信息,容易发生碰撞。而异或运算可以让结果的随机性更大,所以没有使用与或者或。
五、resize()扩容机制     在put方法中,当table数组为空时,会调用resize()方法进行扩容;当map的size大于阈值(threshold)时,也会进行扩容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 final  Node<K,V>[] resize() {                 Node<K,V>[] oldTab = table;                  int  oldCap = (oldTab == null ) ? 0  : oldTab.length;                  int  oldThr = threshold;                  int  newCap, newThr = 0 ;                  if  (oldCap > 0 ) {                          if  (oldCap >= MAXIMUM_CAPACITY) {                 threshold = Integer.MAX_VALUE;                 return  oldTab;             }                          else  if  ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY &&                      oldCap >= DEFAULT_INITIAL_CAPACITY)                 newThr = oldThr << 1 ;          }                                                                                 else  if  (oldThr > 0 )              newCap = oldThr;                           else  {                            newCap = DEFAULT_INITIAL_CAPACITY;             newThr = (int )(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);         }                           if  (newThr == 0 ) {             float  ft = (float )newCap * loadFactor;             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float )MAXIMUM_CAPACITY ?                       (int )ft : Integer.MAX_VALUE);         }                  threshold = newThr;                  @SuppressWarnings({"rawtypes","unchecked"})              Node<K,V>[] newTab = (Node<K,V>[])new  Node[newCap];         table = newTab;                  if  (oldTab != null ) {             for  (int  j = 0 ; j < oldCap; ++j) {                 Node<K,V> e;                                  if  ((e = oldTab[j]) != null ) {                     oldTab[j] = null ;                                                               if  (e.next == null )                         newTab[e.hash & (newCap - 1 )] = e;                                          else  if  (e instanceof  TreeNode)                         ((TreeNode<K,V>)e).split(this , newTab, j, oldCap);                                                               else  {                                                   Node<K,V> loHead = null , loTail = null ;                                                  Node<K,V> hiHead = null , hiTail = null ;                         Node<K,V> next;                         do  {                             next = e.next;                                                                                       if  ((e.hash & oldCap) == 0 ) {                                 if  (loTail == null )                                     loHead = e;                                 else                                      loTail.next = e;                                 loTail = e;                             }                                                          else  {                                 if  (hiTail == null )                                     hiHead = e;                                 else                                      hiTail.next = e;                                 hiTail = e;                             }                         } while  ((e = next) != null );                                                  if  (loTail != null ) {                             loTail.next = null ;                             newTab[j] = loHead;                         }                                                  if  (hiTail != null ) {                             hiTail.next = null ;                             newTab[j + oldCap] = hiHead;                         }                     }                 }             }         }         return  newTab;     } 
    resize函数中,将旧的元素<k,v>移到扩充后的新数组中时,根据元素key的hash值的第log(oldCap)位的不同有两种情况():
    当该位是0时,hash&(newCap-1) = hash&(oldCap-1) ,下标值不变;
    当改为是1时,hash&(newCap-1) = hash&(oldCap-1) + oldCap,下标值会发生变化。
六、get()方法     get()方法比较简单,
1 2 3 4 5 6 7 8 public  V get (Object key)          Node<K,V> e;         return  (e = getNode(hash(key), key)) == null  ? null  : e.value;     } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 final  Node<K,V> getNode (int  hash, Object key)          Node<K,V>[] tab; Node<K,V> first, e; int  n; K k;                  if  ((tab = table) != null  && (n = tab.length) > 0  &&             (first = tab[(n - 1 ) & hash]) != null ) {                          if  (first.hash == hash &&                  ((k = first.key) == key || (key != null  && key.equals(k))))                 return  first;                          if  ((e = first.next) != null ) {                                  if  (first instanceof  TreeNode)                     return  ((TreeNode<K,V>)first).getTreeNode(hash, key);                                  do  {                     if  (e.hash == hash &&                         ((k = e.key) == key || (key != null  && key.equals(k))))                         return  e;                 } while  ((e = e.next) != null );             }         }         return  null ;     } 
七、常见问题 
为什么HashMap链表会形成死循环 
 
        准确的讲应该是 JDK1.7 的 HashMap 链表会有死循环的可能,因为JDK1.7是采用的头插法,在多线程环境下有可能会使链表形成环状,从而导致死循环。JDK1.8做了改进,用的是尾插法,不会产生死循环。
HashMap与HashTable的比较
HashTable使用 synchronized 来进行同步
HashMap 可以插入键为 null 的 Node
HashMap 的迭代器是 fail-fast 迭代器
HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的