概述 hashmap应该是集合中较复杂的一个类。最早出现在1.2中,在1.8中加入了红黑树,所以1.8的改动很大。hashmap允许null值和null键,底层是通过散列算法实现的。并且hashmap不是线程安全的。  
源码分析 成员变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 ;transient  Node<K,V>[] table;transient  int  size;transient  int  modCount;int  threshold;final  float  loadFactor;
 
我记得hashmap的初始大小在android中是4,java中是16…不知道啥时候改了….
构造方法 hashmap的构造方法有4个,具体代码如下:
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 public  HashMap ()   {    this .loadFactor = DEFAULT_LOAD_FACTOR;  } public  HashMap (int  initialCapacity)   {    this (initialCapacity, DEFAULT_LOAD_FACTOR); } 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); } public  HashMap (Map<? extends K, ? extends V> m)   {    this .loadFactor = DEFAULT_LOAD_FACTOR;     putMapEntries(m, false ); } 
 
我们可以在初始化的时候传入初始大小以及扩容因子,如果不传则为默认值。当然,我们还可以把已有map直接传进去。我们可以看到在第三个构造方法中给扩展的限制进行了重新的定义,因为可能我们传的大小不是2的幂数。现在他是如何重新定义的:
1 2 3 4 5 6 7 8 9 10 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的次幂。
添加 现在我们看看hashmap是如何添加一个元素的。  
1 2 3 public  V put (K key, V value)   {    return  putVal(hash(key), key, value, false , true ); } 
 
这边主要是通过putVal进行添加的,这边通过hash函数将key与其高16位异或。具体代码如下:
1 2 3 4 static  final  int  hash (Object key)   {    int  h;     return  (key == null ) ? 0  : (h = key.hashCode()) ^ (h >>> 16 ); } 
 
下面我们具体看看putVal这个方法:
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 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 ;    } 
 
上面注释的已经很清楚了,这边说一下,他会通过散列算法找到对应的位置,如果是空就直接扔进去,如果是非空就判断它是链表还是红黑树,按照对应的数据结构进行插入。
如果链表太长,会进行树化操作,下面看看如何进行树化的:
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 final  void  treeifyBin (Node<K,V>[] tab, int  hash)   {    int  n, index; Node<K,V> e;          if  (tab == null  || (n = tab.length) < MIN_TREEIFY_CAPACITY)         resize();     else  if  ((e = tab[index = (n - 1 ) & hash]) != null ) {                  TreeNode<K,V> hd = null , tl = null ;         do  {                          TreeNode<K,V> p = replacementTreeNode(e, null );             if  (tl == null )                 hd = p;             else  {                 p.prev = tl;                 tl.next = p;             }             tl = p;         } while  ((e = e.next) != null );           if  ((tab[index] = hd) != null )                          hd.treeify(tab);     } } TreeNode<K,V> replacementTreeNode (Node<K,V> p, Node<K,V> next)   {    return  new  TreeNode<>(p.hash, p.key, p.value, next); } 
 
这边代码不多,上面注释已经说的比较清楚了。  
无论在put还是树化中都用到了resize方法,也就是扩容,下面看看resize是如何处理扩容的。
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 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; } 
 
从上述代码,我们可以看出,如果超过了最大值则不需要扩容了,否则将大小以及阈值扩充到原来的两倍。扩充完成后,重新对数据排列。对红黑树以及链表进行拆分。然后按照原顺序进行排列。  
获取 获取其实很简单,就是先定位键所在的桶的位置,然后在对链表或者红黑树进行查找。具体代码如下:
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 public  V get (Object key)   {    Node<K,V> e;     return  (e = getNode(hash(key), key)) == null  ? null  : e.value; } 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 ; } 
 
删除 删除操作其实也是很简单的,第一步,定位bucket的位置,第二步,遍历找到与key相同的节点,第三步,删除。具体代码如下:
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 public  V remove (Object key)   {    Node<K,V> e;     return  (e = removeNode(hash(key), key, null , false , true )) == null  ?         null  : e.value; } final  Node<K,V> removeNode (int  hash, Object key, Object value,                            boolean  matchValue, boolean  movable)   {    Node<K,V>[] tab; Node<K,V> p; int  n, index;     if  ((tab = table) != null  && (n = tab.length) > 0  &&                  (p = tab[index = (n - 1 ) & hash]) != null ) {         Node<K,V> node = null , e; K k; V v;                  if  (p.hash == hash &&             ((k = p.key) == key || (key != null  && key.equals(k))))             node = p;         else  if  ((e = p.next) != null ) {                            if  (p instanceof  TreeNode)                 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);             else  {                                  do  {                     if  (e.hash == hash &&                         ((k = e.key) == key ||                          (key != null  && key.equals(k)))) {                         node = e;                         break ;                     }                     p = e;                 } while  ((e = e.next) != null );             }         }                           if  (node != null  && (!matchValue || (v = node.value) == value ||                              (value != null  && value.equals(v)))) {             if  (node instanceof  TreeNode)                 ((TreeNode<K,V>)node).removeTreeNode(this , tab, movable);             else  if  (node == p)                 tab[index] = node.next;             else                  p.next = node.next;             ++modCount;             --size;             afterNodeRemoval(node);             return  node;         }     }     return  null ; } 
 
总结 合理的使用HashMap能够在增删改查等方面都有很好的表现。在使用时我们需要注意以下几点:
设计的key对象一定要实现hashCode方法,并尽可能保证均匀少重复。
 
由于树化过程会依次通过hash值、比较值和对象的hash值进行排序,所以key还可以实现Comparable,以方便树化时进行比较。
 
如果可以预先估计数量级,可以指定initial capacity,以减少rehash的过程。
 
虽然HashMap引入了红黑树,但它的使用是很少的,如果大量出现红黑树,说明数据本身设计的不合理,我们应该从数据源寻找优化方案。