JDK8源码阅读(七) HashMap TODO

导读:本篇文章讲解 JDK8源码阅读(七) HashMap TODO,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、简介

             本文主要讲解HashMap的原理和部分源码分析以及jdk8做的优化。

             注: 本文章参考博文有:   qazwyc 博主的 Java8 HashMap源码解析 , 尊重原创

二、分析

 

2.1 HashMap的结构

  • 是一个链表散列的数据结构,即 数组和链表的结合体。这样就兼具了寻址容易和增删方便的优点。结构示意图如下:
    • JDK8源码阅读(七) HashMap TODO
  • HashMap内部维护了一个 Node<K,V>[] 的数组table, 然后基于链地址法来解决hash冲突。链地址法将哈希表的每个单元作为链表的头结点,所有哈希地址为 i 的元素构成一个同义词链表entrySet。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。
  • jdk8采用了红黑树优化。

2.2 HashMap的类结构图

JDK8源码阅读(七) HashMap TODO

 

2.3 HashMap的字段

2.3.0 字段列表图

JDK8源码阅读(七) HashMap TODO

 

2.3.1 DEFAULT_INITIAL_CAPACITY

/**
 *  HashMap的初始容量为16
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

 

2.3.2 MAXIMUM_CAPACITY

/**
 * 最大容量,超过这个容量仍是这个容量 2的30次方
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

 

2.3.3 DEFAULT_LOAD_FACTOR

/**
 * 默认的加载因子
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

 

2.3.4 TREEIFY_THRESHOLD

/**
 * jdk8的优化,链表存储转换为红黑树存储节点的阙值
 * 默认当桶内链表节点数量大于8的时候,采用红黑树存储。
 */
static final int TREEIFY_THRESHOLD = 8;

2.3.5 UNTREEIFY_THRESHOLD

/**
 * jdk8的优化,红黑树存储转换为链表存储节点的阙值
 * 默认当桶内链表节点数量小于6的时候,采用链表存储。
 */
static final int UNTREEIFY_THRESHOLD = 6;

 

2.3.6 MIN_TREEIFY_CAPACITY

/**
 * 桶中结构转化为红黑树对应的数组的最小大小,
 * 如果当前容量小于它,就不会将链表转化为红黑树,而是用resize()代替
 */
static final int MIN_TREEIFY_CAPACITY = 64;

2.3.7 table

/**
 * 存储树节点的table,总是2的次方。某些情况允许长度为0  
 */
transient Node<K,V>[] table;

2.3.8 entrySet

/**
 * 存放具体元素的集
 */
transient Set<Map.Entry<K,V>> entrySet;

2.3.9 size

/**
 * map中key-value对的个数
 */
transient int size;

2.3.10 threshold

/**
 * 下次resize扩容的临界量
 * 通过tableSizeFor(cap)方法计算出不小于initialCapacity的最近的2的幂作为初始容量,将其先保 
 * 存在threshold里,当put时判断数组为空会调用resize分配内存,并重新计算正确的threshold
 */
int threshold;

2.3.11 modCount

/**
 * 记录map结构的修改次数,主要用于迭代器的快速失败
 */
transient int modCount;

2.3.12 loadFactor

/**
 * hash table的加载因子
 */
final float loadFactor;

 

2.4 HashMap的内部类列表

2.4.0 内部类列表图

JDK8源码阅读(七) HashMap TODO

2.4.1 Node<K,V>

  • 源码
    • 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;
              }
          }
  • 详解TODO

 

2.5 HashMap的方法列表

2.5.0 方法列表图

JDK8源码阅读(七) HashMap TODO

JDK8源码阅读(七) HashMap TODO

 

2.5.1 构造函数

  • 源码
    • 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;
          // 通过tableSizeFor(cap)计算出不小于initialCapacity的最近的2的幂作为初始容量,将其先保存在threshold里,当put时判断数组为空会调用resize分配内存,并重新计算正确的threshold
          this.threshold = tableSizeFor(initialCapacity);   
      } 
      
      
      public HashMap(int initialCapacity) {
          this(initialCapacity, DEFAULT_LOAD_FACTOR);
      }
      
      
      public HashMap() {
          this.loadFactor = DEFAULT_LOAD_FACTOR; 
      }
      
      //HashMap(Map<? extends K>)型构造函数
      public HashMap(Map<? extends K, ? extends V> m) {
          this.loadFactor = DEFAULT_LOAD_FACTOR;
      
          // 将m中的所有元素添加至HashMap中
          putMapEntries(m, false);
      }
      

       

2.5.2 tableSizeFor(int cap)

  • 源码
    • /**
       * 获取最近的不小于输入容量参数的2的整数次幂。比如容量是10,则返回16
       */    
      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;
      }

       

  • 计算原理如下:
    • JDK8源码阅读(七) HashMap TODO
    •  通过tableSizeFor(),保证了HashMap容量始终是2的次方,在通过hash寻找index时就可以用逻辑运算来替代取余,即hash%n用hash&(n -1)替代。

 

2.5.3 putMapEntries(Map<? extends K, ? extends V> m, boolean evict)

  • 源码
    •     final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
              int s = m.size();
              if (s > 0) {
                  if (table == null) { // pre-size
                      // 根据负载因子和现有大小获取到该集合的最大容量ft  
                      float ft = ((float)s / loadFactor) + 1.0F;
                      // 如果ft小于2的32次方则是ft,否则是2的32次方
                      int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                               (int)ft : MAXIMUM_CAPACITY);
                      // 如果t大于此时记录的下次要扩容的值时,调用方法计算符合的最近的2的N次方的容量
                      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);
                  }
              }
          }

       

2.5.4 hash(Object key)

  • 源码
    • /**
       * 使用key的哈希值与该哈希值的高16位做异或得到哈希值
       */
      static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }

       

  • 计算方式如下图:
    • JDK8源码阅读(七) HashMap TODO
  • 好处:如果直接使用key的hashcode()作为hash很容易发生碰撞。比如,在n – 1为15(0x1111)时,散列值真正生效的只是低4位。当新增的键的hashcode()是2,18,34这样恰好以16的倍数为差的等差数列,就产生了大量碰撞。

    因此,设计者综合考虑了速度、作用、质量,把高16bit和低16bit进行了异或。因为现在大多数的hashCode的分布已经很不错了,就算是发生了较多碰撞也用O(logn)的红黑树去优化了。仅仅异或一下,既减少了系统的开销,也不会造成因为高位没有参与下标的计算(table长度比较小时),从而引起的碰撞。

 

2.5.5 resize()  TODO

  • 源码
    • final Node<K,V>[] resize() {
          // 当前table保存
          Node<K,V>[] oldTab = table;
          // 保存table大小
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          // 保存当前阈值 
          int oldThr = threshold;
          int newCap, newThr = 0;
          // 之前table大小大于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; // double threshold
          }
          // 初始容量已存在threshold中
          else if (oldThr > 0)            // initial capacity was placed in threshold
              newCap = oldThr;
          // 使用缺省值(使用默认构造函数初始化)
          else {                                  // zero initial threshold signifies using defaults
              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"})
          // 初始化table
          Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
          table = newTab;
          // 之前的table已经初始化过
          if (oldTab != null) {
              // 复制元素,重新进行hash
              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)         //红黑树
                          //根据(e.hash & oldCap)分为两个,如果哪个数目不大于UNTREEIFY_THRESHOLD,就转为链表
                          ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                      else { // preserve order
                          Node<K,V> loHead = null, loTail = null;
                          Node<K,V> hiHead = null, hiTail = null;
                          Node<K,V> next;
                          // 将同一桶中的元素根据(e.hash & oldCap)是否为0进行分割成两个不同的链表,完成rehash
                          do {
                              next = e.next;//保存下一个节点
                              if ((e.hash & oldCap) == 0) {       //保留在低部分即原索引
                                  if (loTail == null)//第一个结点让loTail和loHead都指向它
                                      loHead = e;
                                  else
                                      loTail.next = e;
                                  loTail = e;
                              }
                              else {                                      //hash到高部分即原索引+oldCap
                                  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;
      }

       

    • final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
                  TreeNode<K,V> b = this;
                  // Relink into lo and hi lists, preserving order
                  TreeNode<K,V> loHead = null, loTail = null;
                  TreeNode<K,V> hiHead = null, hiTail = null;
                  int lc = 0, hc = 0;
                  for (TreeNode<K,V> e = b, next; e != null; e = next) {
                      next = (TreeNode<K,V>)e.next;
                      e.next = null;
                      if ((e.hash & bit) == 0) {
                          if ((e.prev = loTail) == null)
                              loHead = e;
                          else
                              loTail.next = e;
                          loTail = e;
                          ++lc;
                      }
                      else {
                          if ((e.prev = hiTail) == null)
                              hiHead = e;
                          else
                              hiTail.next = e;
                          hiTail = e;
                          ++hc;
                      }
                  }
      
                  if (loHead != null) {
                      if (lc <= UNTREEIFY_THRESHOLD)
                          tab[index] = loHead.untreeify(map);
                      else {
                          tab[index] = loHead;
                          if (hiHead != null) // (else is already treeified)
                              loHead.treeify(tab);
                      }
                  }
                  if (hiHead != null) {
                      if (hc <= UNTREEIFY_THRESHOLD)
                          tab[index + bit] = hiHead.untreeify(map);
                      else {
                          tab[index + bit] = hiHead;
                          if (loHead != null)
                              hiHead.treeify(tab);
                      }
                  }
              }

       

 

2.5.6 put(K key, V value)

  • 源码 

    •     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.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) // -1 for 1st
                                  treeifyBin(tab, hash);
                              break;
                          }
                          if (e.hash == hash &&
                              ((k = e.key) == key || (key != null && key.equals(k))))
                              break;
                          p = e;
                      }
                  }
                  if (e != null) { // existing mapping for key
                      V oldValue = e.value;
                      if (!onlyIfAbsent || oldValue == null)
                          e.value = value;
                      afterNodeAccess(e);
                      return oldValue;
                  }
              }
              ++modCount;
              if (++size > threshold)
                  resize();
              afterNodeInsertion(evict);
              return null;
          }
    •  
              /**
               * Tree version of putVal.
               */
              final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                             int h, K k, V v) {
                  Class<?> kc = null;
                  boolean searched = false;
                  TreeNode<K,V> root = (parent != null) ? root() : this;
                  for (TreeNode<K,V> p = root;;) {
                      int dir, ph; K pk;
                      if ((ph = p.hash) > h)
                          dir = -1;
                      else if (ph < h)
                          dir = 1;
                      else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                          return p;
                      else if ((kc == null &&
                                (kc = comparableClassFor(k)) == null) ||
                               (dir = compareComparables(kc, k, pk)) == 0) {
                          if (!searched) {
                              TreeNode<K,V> q, ch;
                              searched = true;
                              if (((ch = p.left) != null &&
                                   (q = ch.find(h, k, kc)) != null) ||
                                  ((ch = p.right) != null &&
                                   (q = ch.find(h, k, kc)) != null))
                                  return q;
                          }
                          dir = tieBreakOrder(k, pk);
                      }
      
                      TreeNode<K,V> xp = p;
                      if ((p = (dir <= 0) ? p.left : p.right) == null) {
                          Node<K,V> xpn = xp.next;
                          TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                          if (dir <= 0)
                              xp.left = x;
                          else
                              xp.right = x;
                          xp.next = x;
                          x.parent = x.prev = xp;
                          if (xpn != null)
                              ((TreeNode<K,V>)xpn).prev = x;
                          moveRootToFront(tab, balanceInsertion(root, x));
                          return null;
                      }
                  }
              }

       

    •     /**
           * Replaces all linked nodes in bin at index for given hash unless
           * table is too small, in which case resizes instead.
           */
          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);
              }
          }

       

  • 流程图
    • JDK8源码阅读(七) HashMap TODO

 

2.5.7 get(Object key)  TODO 

  • 源码
    • public V get(Object key) {
         Node<K,V> e;
         // 如果获取到节点,则返回value,否则返回null
         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) {
      
                  // 如果头节点不为空并且头结点的哈希和key的哈希相等并且key也相等,则头节点就命中并返回
                  if (first.hash == hash && // always check first node
                      ((k = first.key) == key || (key != null && key.equals(k))))
                      return first;
      
                  // 如果头结点没有命中
                  if ((e = first.next) != null) {
                      // 如果第一个节点是树节点
                      if (first instanceof TreeNode)
                          // 调用树节点方法获取,时间复杂度O(logn)  见下
                          return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                      // 如果是链表,则判断俩个节点的哈希值和key值是否相等, 如果相等,返回 时间复杂度O(n)
                      do {
                          if (e.hash == hash &&
                              ((k = e.key) == key || (key != null && key.equals(k))))
                              return e;
                      } while ((e = e.next) != null);
                  }
              }
              return null;
          }
    • final TreeNode<K,V> getTreeNode(int h, Object k) {
          // 先获取根节点(如果当前节点父节点为空则为父节点,否则根据当前节点遍历得到根节点)
          // 调用find方法获取节点
          return ((parent != null) ? root() : this).find(h, k, null);
      }

       

    • final TreeNode<K,V> root() {
            // 根据当前节点遍历获取根节点
            for (TreeNode<K,V> r = this, p;;) {
                 if ((p = r.parent) == null)
                    return r;
                 r = p;
            }
      }

       

    •         /**
               * 红黑树定位节点
               */ 
              final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
                  TreeNode<K,V> p = this;
                  do {
                      int ph, dir; K pk;
                      TreeNode<K,V> pl = p.left, pr = p.right, q;
                      if ((ph = p.hash) > h)
                          p = pl;
                      else if (ph < h)
                          p = pr;
                      else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                          return p;
                      else if (pl == null)
                          p = pr;
                      else if (pr == null)
                          p = pl;
                      else if ((kc != null ||
                                (kc = comparableClassFor(k)) != null) &&
                               (dir = compareComparables(kc, k, pk)) != 0)
                          p = (dir < 0) ? pl : pr;
                      else if ((q = pr.find(h, k, kc)) != null)
                          return q;
                      else
                          p = pl;
                  } while (p != null);
                  return null;
              }

       

  • 流程图

 

 

2.5.8 containsKey(Object key) 

  • 源码
    • public boolean containsKey(Object key) {
            // 内部还是调用的getNode方法
            return getNode(hash(key), key) != null;
      }

       

 

2.5.9 containsValue(Object value) TODO

  • 源码
    •     public boolean containsValue(Object value) {
              Node<K,V>[] tab; V v;
              // 直接去遍历 比较value是否有相等的  TODO 疑问:为啥只是链表结构 没有树结构?
              if ((tab = table) != null && size > 0) {
                  for (int i = 0; i < tab.length; ++i) {
                      for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                          if ((v = e.value) == value ||
                              (value != null && value.equals(v)))
                              return true;
                      }
                  }
              }
              return false;
          }

       

  •  

2.5.10 remove(Object key) TODO

  • 源码

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/17732.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!