HashMap源码分析

来源:互联网 发布:淘宝英文版网址 编辑:程序博客网 时间:2024/06/08 17:04

1.hashMap的工作原理

当我们执行put存值时,hashmap会先调用key的hashcode方法的到哈希码,也就是桶的索引bucketIndex,根据哈希码找到该桶,然后遍历桶用equal方法来比较key,如果桶为空,就把kv放入桶中,如果桶存在该key,就用新value代替旧value,返回旧value,如果不存在就放入桶中,每个桶用单链表维护。下面是原理图:

这里写图片描述

 

2.成员变量

要理解成员变量,必须先了解以下知识:

  • 一个HashMap实例有两个参数会影响性能:初始容量(initial capacity)和装载因子(load factor),capacity是hashtable的桶数目,初始容量仅仅只是创建时的容量 。

  • 装载因子是衡量哈希表自动增长前装满的程度,当哈希表中键值对的数目超出容量与装载因子之积,哈希表就会重新哈希(rehashed)(即,内部数据结构会重建)为之前容量的两倍 。

  • 默认的装载因子0.75可以在时间和空间之间有很好的权衡,想避免重新hash的时间开销,可以设置比较大的初始容量。

  • HashMap通常作为桶式哈希表,当桶变得很大的时候就转化为树结点,和TreeMap中比较类似。

    //初始容量,默认16    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;    //最大容量,默认2的30次方    static final int MAXIMUM_CAPACITY = 1 << 30;    //装载因子,默认0.75    static final float DEFAULT_LOAD_FACTOR = 0.75f;    //树形和列表的阀值,大于该值,桶的结构由列表改成链式    static final int TREEIFY_THRESHOLD = 8;    //树形和列表的阀值,小于该值,桶的结构由链式改成列表    static final int UNTREEIFY_THRESHOLD = 6;    //转换树形后最小容量,至少是TREEIFY_THRESHOLD的四倍    static final int MIN_TREEIFY_CAPACITY = 64;    //储存键值对的节点    transient Node<K,V>[] table;    //用作缓存    transient Set<Map.Entry<K,V>> entrySet;    //node的数目    transient int size;   //HashMap的结构更改次数    transient int modCount;    //下次分配空间时,table的大小      int threshold;    //装载因子,定义成final,让它变成不可变成员,只能在构造器初始化。    final float loadFactor;

 

3.构造函数

构造函数用来初始化影响hashmap性能最大的两个参数initialCapacity,loadFactor。

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(int initialCapacity) {          this(initialCapacity, DEFAULT_LOAD_FACTOR);      }      public HashMap() {          this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted      }  

代码很容易看懂,我们来看一下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;    }

 
从这段代码中,方法完成了什么呢? n |= 自身右移多次,完成了将n最高位上的1开始的所有位都置1的操作,例如000101011110就变成了000111111111,也就完成了将利用cap找到2^n-1的数,并将其作为threshold,threhold是下次table执行reset后的大小。这段代码非常巧妙,最后n>>>16的原因是int占32位,16刚好可以满足所有int的需求,不得不说很漂亮的代码。

要避免rehash的时间开销,可以设置比较大的初始容量。否则,如果保持默认的16的话,又有1w对hash,意味着rehash要进行4~5次。
 

4.重要的成员方法

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; Node<K,V> first, e; int n; K k;        //hash表不为空且size大于0,hash所在的桶不为空        if ((tab = table) != null && (n = tab.length) > 0 &&            (first = tab[(n - 1) & hash]) != null) {            //先判断桶的首个Key,相同则返回            if (first.hash == hash && // always check first node                ((k = first.key) == key || (key != null && key.equals(k))))                return first;            //如果首个Key不是,则根据链表遍历或者树遍历找到            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;    }

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;        //如果table数组尚未创建(第一次调用put),则新建table数组          if ((tab = table) == null || (n = tab.length) == 0)            n = (tab = resize()).length;        //table[i]中没有结点则创建新节点          if ((p = tab[i = (n - 1) & hash]) == null)            tab[i] = newNode(hash, key, value, null);        else {            Node<K,V> e; K k;            //如果table[i]的首个元素的关键字与给定关键字key相同,则替换旧值              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 (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;                    }                    //如果找到了,e就不为空,进行下一步                    if (e.hash == hash &&                        ((k = e.key) == key || (key != null && key.equals(k))))                        break;                    p = e;                }            }            //e不为空就是找到了,返回旧值            if (e != null) { // existing mapping for key                V oldValue = e.value;                if (!onlyIfAbsent || oldValue == null)                    e.value = value;                afterNodeAccess(e);                return oldValue;            }        }       //修改map结构的操作数加1          ++modCount;        //如果超出重构阈值,需要重新分配空间          if (++size > threshold)            resize();        afterNodeInsertion(evict);        return null;    }

 

resize方法

resize方法的实现有点复杂,有余力的同学可以认真研究一下:

 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) // 如果oldCap<=0,初始容量为阈值threshold              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);                                          //新表索引:hash & (newCap - 1)---》低x位为Index                                          //旧表索引:hash & (oldCap - 1)---》低x-1位为Index                                          //newCap = oldCap << 1                                          //举例说明:resize()之前为低x-1位为Index,resize()之后为低x位为Index                                          //则所有Entry中,hash值第x位为0的,不需要哈希到新位置,只需要呆在当前索引下的新位置j                                          //hash值第x位为1的,需要哈希到新位置,新位置为j+oldCap                          if (loTail != null) {                              loTail.next = null;                              newTab[j] = loHead;                          }                          if (hiTail != null) {                              hiTail.next = null;                              newTab[j + oldCap] = hiHead;                          }                      }                  }              }          }          return newTab;      }  
原创粉丝点击