美文网首页
深入理解HashMap遍历元素的顺序

深入理解HashMap遍历元素的顺序

作者: 鸿雁长飞鱼龙潜跃 | 来源:发表于2019-06-05 14:47 被阅读0次

    HashMap遍历元素的顺序。

    一,HashMap元素的底层存储顺序

    我们都知道HashMap是“无序”的,也就是说不能保证插入顺序。但是,HashMap其实也是有序的,一组相同的key-value对,无论他们插入的顺序是什么样的,遍历时,顺序都是一致的。

    深入理解HashMap遍历元素的顺序

    上面的代码,分别使用两种方式插入,也就是说插入顺序是不同的,但是遍历的结果是一样的。可以这样理解,只要你插入的是这19个元素,HashMap存储的时候,都是按照同一个顺序来存储的。

    这一点其实也很容易理解,因为HashMap是使用hash算法来定位key的逻辑存储位置,只要你的key是一样的,逻辑存储位置也是一样的。

    遍历结果如下:

    11=11

    12=12

    13=13

    14=14

    15=15

    16=16

    17=17

    18=18

    19=19

    0=0

    1=1

    2=2

    3=3

    4=4

    5=5

    6=6

    7=7

    8=8

    9=9

    10=10

    为什么是这个顺序呢?本人很不理解。

    如果说是根据key的hashcode来定位逻辑存储位置的话,关键key的hashcode也不支持这种说法啊。key的hashcode打印如下:

    1568

    1569

    1570

    1571

    1572

    1573

    1574

    1575

    1576

    49

    50

    51

    52

    53

    54

    55

    56

    57

    1567

    HashMap是通过hash算法把key映射到table数组的某个位置。以这个例子为例来说明,HashMap初始化容量指定为20,最终HashMap会把容量转化为32,那么这个HashMap的数组是一个长度为32的数组。

    (为什么?稍后会说明)

    OK,我们有一个长度为32的数组,接下来我们插入第一个元素,也就是key为0的一个字符串,这个key的hashcode值为48。那么这个key会被映射到数组的哪个位置呢?这个key对应数组的下标是多少呢?

    这里有一个问题需要注意,长度为32的数组的下标必然是从0到31。然后这个数组的遍历顺序也是从0到31的顺序开始遍历。

    查看源码我才发现,原来,数组的下标并不是key的hashcode值,而是key的hashcode值再经过按位与逻辑运算得到的。

    先看一下源码,其他的代码我这里就不贴了,我只看最下面这行最关键的代码,如下图。

    深入理解HashMap遍历元素的顺序

    newTab[e.hash & (newCap - 1)] = e;

    就是这行代码,这行代码就是把新增的节点Node放到数组中,放到数组中的哪个位置呢?

    放到下标=e.hash & (newCap - 1)的位置!

    好,那我就把e.hash & (newCap - 1)打印出来,看看到底是什么?

    因为我们有20个元素,HashMap最终扩容以后的大小是32,也就是说:

    newCap = 32

    e. hash刚才说了,就是key的hashcode值,好,打印看看到底是什么鬼!

    0-1-2-3-4-5-6-7-8-16-17-18-19-20-21-22-23-24-25-31

    这就是结果!

    这就是HashMap元素的存储顺序!

    这就是HashMap元素的底层逻辑地址!

    这!我的疑惑总算解开了!

    下面再说一下2个关键问题:

    1,指定的size大小是20,容量大小为什么变成了32。

    2,HashMap为什么不直接用key的hashcode值来定位数组下标。

    二,HashMap扩容的容量大小问题

    HashMap初始化时,如果不指定,那么默认的容量大小为16。源代码中是这样的:

    DEFAULT_INITIAL_CAPACITY = 1 << 4

    但是我指定了容量为20,为什么就变成了32呢?

    想搞清楚这个问题,必须清楚HashMap容量大小的计算逻辑。

    源码如下:

    深入理解HashMap遍历元素的顺序

    这个方法是一系列的逻辑位运算。反正我现在是看不懂了,不过没关系,我把代码拿过来跑一下看看结果。

    当入参是16的时候,返回16。

    当入参是20的时候,返回是32。

    原来如此啊,HashMap的容量大小必须是2的N次方。

    结论:HashMap的容量必须是2的N次方,如果你指定了一个非2的N次方的整数S,那么HashMap在内部会把它转化为大于S的2的N次方的整数。

    当然,这个整数是大于S的2的N次方的整数中最小的那一个。

    三,HashMap为什么不直接用key的hashcode值来定位数组下标。


    原因其实很简单,因为本例中HashMap的数组是一个长度为32的数组,下标从0到31。

    key的hashcode值超过了31,就不行了。

    所以,java的开发者做了一个映射,把key的hashcode值映射到0到31的区间范围。如何映射的呢?就是上面提到的:

    e.hash & (newCap - 1)

    把key的hashcode值与31做位与操作。

    java的位与操作,忘记的可以自行脑补,我这里不在赘述,网上有很多讲解。我这里说一下java设计者的实现思路,为什么要这样实现。

    为什么呢?因为HashMap是可以扩容的,newCap这个变量是动态的,本例中是32,但是如果是64呢,如果是256呢?

    这时我就把这个newCap当做一个变量,接受用户输入。这样就成功把key的hashcode值映射到了指定范围的区间内:

    【0到newCap减1】

    这里的位与操作是关键,我们想把一些随机的整数映射到指定范围的区间时,可以考虑使用位与操作。这是计算机逻辑运算的特性,java的开发者们很好的利用了这一特性。

    好了,今天的分享到此结束。

    相关文章

      网友评论

          本文标题:深入理解HashMap遍历元素的顺序

          本文链接:https://www.haomeiwen.com/subject/vhxsxctx.html