HashMap本质是一个链表散列的数据结构,即数组和链表的结合体。HashMap底层是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

transient Entry[] table;
static class Entry<K, V> implements Map.Entry<K, V>{
    final K key;
    V value;
    Entry<K, V>next;
    final int hash;
    .....
}

  put方法

    先根据key算出hash值,然后根据得到的hash值来定位这个元素在数组中的位置,若数组该位置已经存放了其他元素,那么这个位置上的元素将以链表的形式存放,新加入的放在链表头,最先加入的放在链尾,如果数组在该位置没有元素,则将该元素放到数组中。

public V put(K key, V value){
    if(key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for(Entry<K, V> e = table[i]; e != null; ){
        Object k;
        if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}    

void addEntry(int hash, K key, V vaule, int bucketIndex){
  Entry<K, V> e = table[bucketIndex];
  table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
  if(size ++ >= threshold)
    resize(2 * table.length);
}

static int hash(int h){
  h ^= (h >>> 20) ^ (h >>> 12);
  return h ^ (h >>> 7) ^ (h >>> 4);
}

static int indexFor(int h, int length){
  return h & (length - 1);
}

  get()方法:先根据key算出hash值,然后根据hash值找到对应位置的某一元素,然后通过key的equlas方法在对应位置的链表中找到需要的元素

public V get(Object key){
    if(key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for(Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next){
      Object k;
     if(e.hash == hash && ((k = e.key) == key || key.equals(k)))
       return e.value; }
   return null; }

 

  当HashMap中的元素个数超过数组大小*loadFactor时,就会对数组进行扩容,扩大一倍后重新计算每个元素在数组中的位置。loadFactor的默认值是0.75。

 

  loadFactor = 散列表的实际元素数目/散列表的容量。负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),若负载因子太大,对空间利用充分,但查找效率较低;若负载因子太小,那么散列表的数据将过于稀疏,对空间进行严重浪费。

  threshold = (int) (capacity * loadFactor);超过threshold会重新resize,以降低实际的负载因子

 

  java.util.HashMap是不安全的,若在使用迭代器的过程中有其他线程修改了map,则抛出ConcurrentModificationException,这是所谓的fail-fast策略。每次对HashMap内容的修改都将增加modCount域,在迭代器初始化过程中会将这个值赋给迭代器expectedModCount。

HashIterator(){
    expectedModCount = modCount;
    if(size  > 0) {
        Entry[] t = table;
        while(index <  t.length && (next = t[index++]) == null)
            ;
    }

   final Entry<K, V> nextEntry(){
     if(modCount != expectedModCount)
       throw new ConcurrentModificationException();
   } }

  在所有HashMap类的collection视图方法所返回的迭代器都是fail-fase的:在迭代器创建之后,若从结构上对映射进行修改,除非通过迭代器本身的remove方法,其他任何时间任何方式的修改,迭代器都会抛出ConcurrentModificationException。

 

  JDK1.8改版HashMap  

    从结构实现上由数组+链表改为数组+链表+红黑树(当链表长度大于8时转换为红黑树)

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){...}
 
  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()方法

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)
    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.has == 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;
}

   resize():重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩容。Java的数组是无法自动扩容的,只好使用一个新的大的数组代替已有的容量小的数组。扩容时使用的是2次幂的扩展,因此元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。在JDK1.8中就无需重新计算hash,只需要看原来的hash值新增的那个bit是1还是0,是0的话索引没变,是1的话索引编程“原索引 + oldCap”。

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", "uncheck"})
  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.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; }

  get()方法

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;
  if((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n -1) & hash]) != null){
    if(first.hash == hast && ((k = first.ley) == 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;
}