9-HashMap底层结构和源码分析

打印 上一主题 下一主题

主题 919|帖子 919|积分 2757

9-HashMap底层结构和源码分析

1-HashMap底层结构阐明


  • HashMap 底层维护的是数组 + 链表 + 红黑树,(jdk 7 版本的 HashMap 底层实现(数组 + 链表),jdk 8 版本底层实现(数组 + 链表 + 红黑树) )。
  • 数组中存放的是 HashMap 的内部类 Node (实现 Map.Entry)数据结点。
  • 扩容机制

    • HashMap 底层维护了 Node 类型数组 table ,默认为 null 。
    • 当创建对象时,将加载因子(loadfactor)初始化为 0.75。
    • 当添加 Key-Value 时,通过 Key 的哈希值得到在 table 中的索引。然后判定该索引处是否有元素,假如没有元素直接添加。假如该索引处有元素,继续判定该元素的 Key 是否和预备加入的 Key 相等,假如相等,则直接替换 Value ;若果不想等需要判定是树结构还是链表结构,做出相应的处理。假如添加时发现容量不敷,则需要扩容。
    • 首次添加,则需要扩容 table 为 16,临界值 threshold 为 12.
    • 以后扩容,则为原来容量的2倍,临界值也是一样。
    • 在 Java 8 中,若一条链表的元素个数凌驾 TREEIFY_THRESHOLD (默认链表树化的门槛或阈值为 8 ),并且 表 table 的容量 => MIN_TREEIFY_CAPACITY(默认表树化的门槛或阈值为 64),就会进行树化(红黑树)。

  • HashMap 添加元素底层源码流程分析


  • HashMap 底层源码分析
  1. // 添加元素的核心方法有 hash、putval、resize、treeifyBin、putTreeVal
  2. // hash
  3. // 根据传入的 Key 对象生成对应的 hash 值
  4. // 若为 null ,则 hash = 0
  5. // 若不为 null ,则先获取 Key 对象的 hashcode 值然后按位异或 hashcode 值的逻辑右移 16 位
  6. static final int hash(Object key) {
  7.         int h;
  8.         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  9. }
  10.     // resize
  11.     // 扩容
  12.     final Node<K,V>[] resize() {
  13.         // 获取当前表
  14.         Node<K,V>[] oldTab = table;
  15.         // 获取当前表容量
  16.         // 若当前表为空,说明当前表可能是刚初始化的表,则表的容量也为 0
  17.         // 若当前表不为空,当前表的容量为 oldTab.length
  18.         int oldCap = (oldTab == null) ? 0 : oldTab.length;
  19.         // 获取当前扩容警戒值
  20.         int oldThr = threshold;
  21.         // 初始化当前表扩容后的新容量、新扩容警戒值
  22.         int newCap, newThr = 0;
  23.         // 判断当前表的容量,若是满足此种情况,说明已经扩过容了,不是初始化状态了
  24.         // 嵌套条件判断
  25.         // (1) 当前表达到或超出最大默认容量,仅需改变当前扩容警戒值,然后返回当前表
  26.         // (2) 按当前表的容量扩容二倍给新表且小于默认最大容量,同时还满足当前表大于默认初始容量(16),
  27.         //            则新扩容警戒值变为原来的 2 倍,等待之后进行扩容即可
  28.         if (oldCap > 0) {
  29.             if (oldCap >= MAXIMUM_CAPACITY) {
  30.                 threshold = Integer.MAX_VALUE;
  31.                 return oldTab;
  32.             }
  33.             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  34.                      oldCap >= DEFAULT_INITIAL_CAPACITY)
  35.                 newThr = oldThr << 1; // double threshold
  36.         }
  37.         // 判断当前扩容警戒值,满足这一条件说明是通过 HashMap(int) 或者 HashMap(int,float) 初始化的 HashMap
  38.         // 将原本在初始化的 threshold 的值(这就是指定的初始化容量)赋给 newCap
  39.         // 此时,处理完 newCap 等待后续处理 newThr ,在等待扩容就行了
  40.         else if (oldThr > 0) // initial capacity was placed in threshold
  41.             newCap = oldThr;
  42.         // 此为 HashMap()初始化的 HashSet 进行首次扩容
  43.         // 等待扩容即可
  44.         else {               // zero initial threshold signifies using defaults
  45.             newCap = DEFAULT_INITIAL_CAPACITY;
  46.             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  47.         }
  48.         // 此判断是为了处理通过 HashMap(int) 或者 HashMap(int,float) 初始化的 HashMap
  49.         // 的 newThr ,完毕之后等待扩容即可
  50.         if (newThr == 0) {
  51.             float ft = (float)newCap * loadFactor;
  52.             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  53.                       (int)ft : Integer.MAX_VALUE);
  54.         }
  55.         // 将扩容后的扩容警戒值赋给实际对象的扩容警戒值
  56.         threshold = newThr;
  57.         @SuppressWarnings({"rawtypes","unchecked"})
  58.         // 根据 newCap 进行扩容
  59.         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  60.         // 将扩容后的新表赋给实际对象的表
  61.         table = newTab;
  62.         // 将原有表的数据全部复制到当前新表(上一步已替换)
  63.         if (oldTab != null) {
  64.             for (int j = 0; j < oldCap; ++j) {
  65.                 Node<K,V> e;
  66.                 if ((e = oldTab[j]) != null) {
  67.                     oldTab[j] = null;
  68.                     if (e.next == null)
  69.                         newTab[e.hash & (newCap - 1)] = e;
  70.                     else if (e instanceof TreeNode)
  71.                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  72.                     else { // preserve order
  73.                         Node<K,V> loHead = null, loTail = null;
  74.                         Node<K,V> hiHead = null, hiTail = null;
  75.                         Node<K,V> next;
  76.                         do {
  77.                             next = e.next;
  78.                             if ((e.hash & oldCap) == 0) {
  79.                                 if (loTail == null)
  80.                                     loHead = e;
  81.                                 else
  82.                                     loTail.next = e;
  83.                                 loTail = e;
  84.                             }
  85.                             else {
  86.                                 if (hiTail == null)
  87.                                     hiHead = e;
  88.                                 else
  89.                                     hiTail.next = e;
  90.                                 hiTail = e;
  91.                             }
  92.                         } while ((e = next) != null);
  93.                         if (loTail != null) {
  94.                             loTail.next = null;
  95.                             newTab[j] = loHead;
  96.                         }
  97.                         if (hiTail != null) {
  98.                             hiTail.next = null;
  99.                             newTab[j + oldCap] = hiHead;
  100.                         }
  101.                     }
  102.                 }
  103.             }
  104.         }
  105.         return newTab;
  106.     }
  107.    
  108.     // putval
  109.     // 存储数据
  110.     final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  111.         Node<K,V>[] tab; Node<K,V> p; int n, i;
  112.         // 判断当前表
  113.         // 1. 获取当前表,并确认是否为空,说明当前表可能为刚初始化的 HashMap (HashSet)
  114.         // 2. 获取当前表的长度,并确认是否为空,说明当前表可能执行过 clear 方法
  115.         // 满足任一条件后,进行容量处理,并获取处理后的表的长度
  116.         if ((tab = table) == null || (n = tab.length) == 0)
  117.             n = (tab = resize()).length;
  118.         // 根据 hash 值计算索引值,并获取索引上的数据元素,然后判断是否为空
  119.         // 若为空,直接将该数据元素插入该索引
  120.         if ((p = tab[i = (n - 1) & hash]) == null)
  121.             tab[i] = newNode(hash, key, value, null);
  122.         // 若不为空,开始进行数据比对
  123.         else {
  124.             Node<K,V> e; K k;
  125.             // 先与索引上的数据结点进行比对
  126.             // 以下是相同的数据元素,会直接赋给 e ,然后返回旧值,代表元素重复
  127.             // 从 hash 和 key 或者 equals(可自定义) 来进行判断
  128.             // 相同元素的 hash 值是相同的,所以这个要注意有个哈希值控制方法
  129.             // 不同对象的哈希值一定是不同的,但 String 对象除外,之前介绍过
  130.             // 而且可以通过重写 equals 和 hashCode 来达到对于同一类的对象如何
  131.             // 为相同的可以进行控制
  132.             if (p.hash == hash &&
  133.                 ((k = p.key) == key || (key != null && key.equals(k))))
  134.                 e = p;
  135.             // 树结点的实例
  136.             // 是的话进行树结点的元素比对和存储
  137.             else if (p instanceof TreeNode)
  138.                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  139.             // 链表
  140.             else {
  141.                 // binCount 是统计链表的元素个数,不包括索引上的数据结点
  142.                 for (int binCount = 0; ; ++binCount) {
  143.                     // 查看 p 是否有下一个结点
  144.                     // 若为 null 的话,直接插入元素即可
  145.                     // 然后再来判断链表元素个数是否达到树化条件
  146.                     // 结束循环,之后处理 e
  147.                     if ((e = p.next) == null) {
  148.                         p.next = newNode(hash, key, value, null);
  149.                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  150.                             treeifyBin(tab, hash);
  151.                         break;
  152.                     }
  153.                     // p 的下一个结点不为 null
  154.                     // 进行元素比对,和之前的方式一样
  155.                     // 元素一致的话就结束循环,之后处理 e
  156.                     if (e.hash == hash &&
  157.                         ((k = e.key) == key || (key != null && key.equals(k))))
  158.                         break;
  159.                     p = e;
  160.                 }
  161.             }
  162.             // 判断 e 是否存在值
  163.             // 若有的话,则说明有重复元素
  164.             if (e != null) { // existing mapping for key
  165.                 V oldValue = e.value;
  166.                 if (!onlyIfAbsent || oldValue == null)
  167.                     e.value = value;
  168.                 afterNodeAccess(e);
  169.                 return oldValue;
  170.             }
  171.         }
  172.         // 每一次修改都会统计
  173.         ++modCount;
  174.         // 修改成功后就将大小 + 1
  175.         // 然后查看大小是否达到警戒扩容值
  176.         // 这个警戒扩容值是针对存储了多少元素的,不是仅对于表中存储的
  177.         // 数据个数进行是否扩容的,还包括其链表或者红黑树的数据元素的
  178.         if (++size > threshold)
  179.             resize();
  180.         afterNodeInsertion(evict);
  181.         // 返回 null 意味着成功插入
  182.         return null;
  183.     }
  184. // treeifyBin 树化(红黑树)
  185. // 树化需要满足链表个数(8)和表的容量(>= 64)
  186. // 当满足链表个数的话,表容量不满足,就先进行表容量的扩容
  187. // 还有一个重要点就是当红黑树的个数小于等于 6 的话,红黑树又会转成链表,此过程称为剪枝
  188. // 关于剪枝仅是提一下
  189.     final void treeifyBin(Node<K,V>[] tab, int hash) {
  190.         int n, index; Node<K,V> e;
  191.         if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  192.             resize();
  193.         else if ((e = tab[index = (n - 1) & hash]) != null) {
  194.             TreeNode<K,V> hd = null, tl = null;
  195.             do {
  196.                 TreeNode<K,V> p = replacementTreeNode(e, null);
  197.                 if (tl == null)
  198.                     hd = p;
  199.                 else {
  200.                     p.prev = tl;
  201.                     tl.next = p;
  202.                 }
  203.                 tl = p;
  204.             } while ((e = e.next) != null);
  205.             if ((tab[index] = hd) != null)
  206.                 hd.treeify(tab);
  207.         }
  208.     }
复制代码

  • HashMap 扩容机制实践练习
[code]package map;import java.util.HashMap;public class HashMapIncrement {    @SuppressWarnings({"all"})    public static void main(String[] args) {        HashMap hashMap = new HashMap();        // HashMap ,使用差别的数字(hash 一般也不相同)        // 所以存储的位置都在内部数组表上,仅模仿内部数组表的扩容        /*for (int i = 1 ; i

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

尚未崩坏

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表