• 阿里面试题:为什么Map桶中个数超过8才转为红黑树


    为什么一个是8一个是6:防止频繁来回转换小消耗性能

    这是笔者面试阿里时,被问及的一个问题,应该不少人看到这个问题都会一面懵逼。因为,大部分的文章都是分析链表是怎么转换成红黑树的,但是并没有说明为什么当链表长度为8的时候才做转换动作。笔者第一反应也是一样,只能初略的猜测是因为时间和空间的权衡。

    要弄明白这个问题,我们首先要明白为什么要转换,这个问题比较简单,因为Map中桶的元素初始化是链表保存的,其查找性能是O(n),而树结构能将查找性能提升到O(log(n))。当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成树。至于为什么阈值是8,我想,去源码中找寻答案应该是最可靠的途径。

    8这个阈值定义在HashMap中,如下所示,这段注释只说明了8是bin(bin就是bucket,即HashMap中hashCode值一样的元素保存的地方)从链表转成树的阈值,但是并没有说明为什么是8:

    复制代码
    1 /**
    2  * The bin count threshold for using a tree rather than list for a
    3  * bin.  Bins are converted to trees when adding an element to a
    4  * bin with at least this many nodes. The value must be greater
    5  * than 2 and should be at least 8 to mesh with assumptions in
    6  * tree removal about conversion back to plain bins upon shrinkage.
    7  */
    8 static final int TREEIFY_THRESHOLD = 8;
    复制代码

    我们继续往下看,在HashMap中有一段Implementation notes,笔者摘录了几段重要的描述,第一段如下所示,大概含义是当bin变得很大的时候,就会被转换成TreeNodes中的bin,其结构和TreeMap相似,也就是红黑树:

    This map usually acts as a binned (bucketed) hash table, but
    when bins get too large, they are transformed into bins of TreeNodes,
    each structured similarly to those in java.util.TreeMap

    继续往下看,TreeNodes占用空间是普通Nodes的两倍,所以只有当bin包含足够多的节点时才会转成TreeNodes,而是否足够多就是由TREEIFY_THRESHOLD的值决定的。当bin中节点数变少时,又会转成普通的bin。并且我们查看源码的时候发现,链表长度达到8就转成红黑树,当长度降到6就转成普通bin。

    这样就解析了为什么不是一开始就将其转换为TreeNodes,而是需要一定节点数才转为TreeNodes,说白了就是trade-off,空间和时间的权衡

    复制代码
     1 Because TreeNodes are about twice the size of regular nodes, we
     2 use them only when bins contain enough nodes to warrant use
     3 (see TREEIFY_THRESHOLD). And when they become too small (due to
     4 removal or resizing) they are converted back to plain bins.  In
     5 usages with well-distributed user hashCodes, tree bins are
     6 rarely used.  Ideally, under random hashCodes, the frequency of
     7 nodes in bins follows a Poisson distribution
     8 (http://en.wikipedia.org/wiki/Poisson_distribution) with a
     9 parameter of about 0.5 on average for the default resizing
    10 threshold of 0.75, although with a large variance because of
    11 resizing granularity. Ignoring variance, the expected
    12 occurrences of list size k are (exp(-0.5)*pow(0.5, k)/factorial(k)). 
    13 The first values are:
    14 0:    0.60653066
    15 1:    0.30326533
    16 2:    0.07581633
    17 3:    0.01263606
    18 4:    0.00157952
    19 5:    0.00015795
    20 6:    0.00001316
    21 7:    0.00000094
    22 8:    0.00000006
    23 more: less than 1 in ten million
    复制代码

    这段内容还说到:当hashCode离散性很好的时候,树型bin用到的概率非常小,因为数据均匀分布在每个bin中,几乎不会有bin中链表长度会达到阈值。但是在随机hashCode下,离散性可能会变差,然而JDK又不能阻止用户实现这种不好的hash算法,因此就可能导致不均匀的数据分布。不过理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,我们可以看到,一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。所以,之所以选择8,不是拍拍屁股决定的,而是根据概率统计决定的。由此可见,发展30年的Java每一项改动和优化都是非常严谨和科学的。

    • 画外音

    笔者通过搜索引擎搜索这个问题,发现很多下面这个答案(猜测也是相互转发):

    红黑树的平均查找长度是log(n),如果长度为8,平均查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,而log(6)=2.6,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

    笔者认为这个答案不够严谨:“3相比4有转换的必要,而2.6相比3就没有转换的必要?

     所以我认为答这个题从下面3点:

    1.TreeNodes占用空间是普通Nodes的两倍,为了空间和时间的权衡,为6时红黑树也比链表快,但转换过程消耗和空间消耗不划算

    2.节点的分布频率会遵循泊松分布,链表长度达到8个元素的概率为0.00000006,几乎是不可能事件

    3.提出来回转化的阈值8和6阈值为什么不一样

    至于为什么转化为红黑树的阈值8和转化为链表的阈值6不一样,是为了避免频繁来回转化

    https://www.cnblogs.com/linghu-java/p/10598758.html

  • 相关阅读:
    查询端口号的连接情况
    mac环境下安装python3的requests包
    Apollo配置中心介绍
    简单工厂,工厂方法,抽象工厂模式对比
    全表查询sql执行链路排查
    logstash部署及项目日志输出到ES
    【SpringMVC入门系列】篇4:SpringMVC传值方式
    【设计模式】外观模式
    【SpringMVC入门系列】篇3:@RequestMapping & @RequestHeader & @CookieValue详解与REST风格请求
    【SpringMVC入门系列】篇1:SpringMVC基本使用与执行流程
  • 原文地址:https://www.cnblogs.com/twoheads/p/10667449.html
Copyright © 2020-2023  润新知