面试官:讲一下HashMap中的get方法,最好可以详细的讲一下getTreeNode方法的具体实现

1,195 阅读5分钟

前言

点赞在看,养成习惯。

点赞收藏,人生辉煌。

点击关注【微信搜索公众号:编程背锅侠】,防止迷路。

HashMap系列文章

第一篇 HashMap源码中的成员变量你还不懂? 来来来!!!整理好的成员变量源码解析

第二篇 撸啊撸,再次撸HashMap源码,踩坑源码中构造方法!!!每次都有收获

第三篇 MoxiMoxi !!!你看过HashMap中的put方法的源码吗?

第四篇 HashMap源码中的resize扩容方法除了扩容还有一个用途你真的知道吗?

第五篇 留一半清醒、留一半醉!!!HashMap中treeifyBin、treeify源码解析

第六篇 隔一段时间撸一次,特别香,HashMap中remove、getOrDefault源码,一遍一遍、又一遍

第六篇 面试官:讲一下HashMap中的get方法,最好可以详细的讲一下getTreeNode方法的具体实现

查找元素的方法

案例演示-普通查找
@Test
public void test_map_hash_get() {
 HashMap<Integer, String> map = new HashMap<>();
 map.put(1, "aaa");
 map.put(2, "bbb");
 map.put(3, "bbb111");

 String s = map.get(2);
 System.out.println(s);
}
案例演示-链表查找
@Test
public void test_map_link_get(){
 HashMap<String, Integer> map = new HashMap<>();
 for (int i = 0; i < 64; i++){
  map.put("BB" + i, i);
 }
 System.out.println(map.size());
 map.put("3Qj", 800);
 map.put("2pj", 801);
 map.put("2qK", 802);
 map.put("2r,", 803);
 map.put("3RK", 804);
 map.put("3S,", 805);
 map.put("42j", 806);
 map.put("43K", 807);

 Integer x = map.get("42j");
 System.out.println(x);
}
案例演示-红黑树查找
@Test
public void test_map_link_tree(){
 HashMap<String, Integer> map = new HashMap<>();
 for (int i = 0; i < 64; i++){
  map.put("BB" + i, i);
 }
 System.out.println(map.size());
 map.put("3Qj", 800);
 map.put("2pj", 801);
 map.put("2qK", 802);
 map.put("2r,", 803);
 map.put("3RK", 804);
 map.put("3S,", 805);
 map.put("42j", 806);
 map.put("43K", 807);
 map.put("44,", 808);
 
 Integer x = map.get("43K");
 System.out.println(x);
}

源码分析

根据key查找对应的值
// 根据key查询对应的val
public V get(Object key) {
  // 定义一个node节点
 Node<K,V> e;
  // 根据key获取的节点为null返回这个null,否则获取的节点不为null,返回这个节点对应的值
 return (e = getNode(hash(key), key)) == null ? null : e.value;
}
根据哈希值和指定的key获取节点
// 根据指定的key和val获取节点
final Node<K,V> getNode(int hash, Object key) {
 Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  // 如果哈希表不为空并且key对应的桶上不为空
 if ((tab = table) != null && (n = tab.length) > 0 &&
   (first = tab[(n - 1) & hash]) != null) {
    // 判断数组元素是否相等
    // 根据索引的位置检查第一个节点
    // 注意:总是检查第一个节点
  if (first.hash == hash && // always check first node
    ((k = first.key) == key || (key != null && key.equals(k))))
      // 如果是这个节点,返回这个节点
   return first;
    // 如果不是第一个节点,判断是否有后续节点
  if ((e = first.next) != null) {
      // 判断是否是红黑树,是的话调用红⿊树中的getTreeNode方法获取节点
   if (first instanceof TreeNode)
    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
   do {
        // 不是红黑树的话,那就是链表结构了,通过循环的方法判断链表中是否存在该key
    if (e.hash == hash &&
      ((k = e.key) == key || (key != null && key.equals(k))))
          // 返回这个节点
     return e;
   } while ((e = e.next) != null);
  }
 }
  // 未获取到节点返回null
 return null;
}
根据指定的哈希值和key获取树节点
// 参数h为哈希值,参数k为指定的key
final TreeNode<K,V> getTreeNode(int h, Object k) {
  // ((parent != null) ? root() : this)获取跟节点
  // 从跟节点开始查找指定的key
 return ((parent != null) ? root() : this).find(h, k, null);
}
根据哈希值和指定的key查询节点
// h:哈希值。 k:给定的key
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
  // 首次遍历this是桶里面的第一个节点
 TreeNode<K,V> p = this;
  // 循环遍历这棵红黑树
 do {
  int ph, dir; K pk;
    // pl当前节点p的左孩子,pr当前节点p的右孩子
  TreeNode<K,V> pl = p.left, pr = p.right, q;
    // 将当前节点的哈希值赋值给ph,判断给定的哈希值是否小于当前节点的哈希值
  if ((ph = p.hash) > h)
      // 将左孩子赋值给p
   p = pl;
    // 当前节点的哈希值小于给定的哈希值
  else if (ph < h)
      // 将右孩子赋值给p
   p = pr;
    // 判断当前节点的key赋值给pk,并且pk与给定的k相等
  else if ((pk = p.key) == k || (k != null && k.equals(pk)))
      // 找到之后直接返回
   return p;
    // 判断左孩子是否null
  else if (pl == null)
      // 将右孩子赋值给p
   p = pr;
    // 判断右孩子pr是否为null
  else if (pr == null)
      // 将左孩子pl赋值给p
   p = pl;
    // 经过compare计算出dir
  else if ((kc != null ||
    (kc = comparableClassFor(k)) != null) &&
    (dir = compareComparables(kc, k, pk)) != 0)
      // 如果计算出的dir小于0,将pl左孩子赋值给p,否则将pr右孩子赋值给p 
   p = (dir < 0) ? pl : pr;
    // 递归查找,查找到结果赋值给q
  else if ((q = pr.find(h, k, kc)) != null)
      // 返回q
   return q;
  else
      // 将左孩子赋值给p
   p = pl;
    // 判断p是否为空,不为空接着循环
 } while (p != null);
  // 没有获取到节点直接返回
 return null;
}
get方法的实现步骤
1、通过hash值获取该key映射到的桶
2、桶上的key就是要查找的key,则直接找到并返回
3、桶上的key不是要找的key,则查看后续的节点:
 - 如果后续节点是红黑树节点,通过调用红⿊树的方法根据key获取value
  - 如果后续节点是链表节点,则通过循环遍历链表根据key获取value
4、查找红黑树,由于之前添加时已经保证这个树是有序的了,因此查找时基本就是折半查找,效率更更高。

5、这⾥和插⼊时一样,如果对比节点的哈希值和要查找的哈希值相等,就会判断key是否相等,相等就直接返回。不相等就从⼦树中递归查找。
 - 若为树,则在树中通过key.equals(k)查找,O(logn)
 - 若为链表,则在链表中通过key.equals(k)查找,O(n)。

谢谢点赞

  • 创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆
  • 你的点赞、评论以及关注是对我最大的支持和鼓励
  • 是我继续创作高质量博客的动力== !!!==

本文使用 mdnice 排版