容量和扩容
1 | /** |
我们这里能看到,和 HashTable 一样有 Capacity 和 Factor 的设定,这里面我们的默认值也是 16 和 0.75 的值。
1 | /** |
这个就是我们说的链表成树的门限,我们在大小超过 THRESHOLD(8) 的时候会把列表变成一棵树,还有 UNTREEIFY_THRESHOLD 就是树返回链表的门限,MIN_TREEIFY_CAPACITY 这个是树化的最小的 HashMap 容量。
数据存储
1 | static class Node<K,V> implements Map.Entry<K,V> { |
我们每一个 Node 都是这样的一个数据节点,包含K-V和子节点,这个和 Hashtable 没啥区别。
hash方法
1 | /** |
JDK7、JDK8 的 HashMap 的 hash 方法是不同的,但是实现的原理其实是一样的东西,我们在程序中实现了一个扰动函数,在这里面我们解释一下这个扰动函数的具体实现细节:
我们先使用默认的HashCode方法求出了返回的Int类型的hash,但是我们的 hash 本身是一个 32bit 的数据,二次幂的话,会有 40亿的 hash 空间,但是 40亿 的 hash 空间是没办法存储的开的,所以我们本身就用通过掩模的方式去的降低我们的数位。
我们先右移 16 位和自己的 hash 本身异或一下,能让我们的数据更为分散一些。这也揭示了我们的 HashMap 的大小为什么都是取 2 的次幂进行处理,那样我们的 (table.length ‐ 1) 能成为我们的 低位掩模 。我们最后在进行一次 (table.length - 1) & hash 之后我们就能求出位数较小的 hash 值。我们通过 >>> 的方式,让我们的低位值也能保留出高位数据的值,能让我们的 hash 更加平均。
Field数据
1 | /** |
这些数据我们都很熟悉,各种的存储的 table ,用作缓存的 entrySet,还有用作判断同步的 modCount 参数,还有门限和加载因子。
1 | /** |
我们在构造函数中通过这个算法找到了大于等于我们给定参数的最小的二次幂方的参数,这个算法的分析很多人节写的比较麻烦,我们可以简单地理解为我们收到的结果参数都是将数位的所有的位数都变成 1,这样子二进制再加以就会进位,那就肯定是一个 1 全 0 的情况,然后我们拿到值就全是 2 的幂了,这样和 之前介绍的 hash 方法的结合,我们的 HashMap 的容量就能被控制在 2 的幂次了。