为什么使用 1<<4 而不是 16?
的 OpenJDK 代码包括以下行:java.util.HashMap
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
为什么在这里使用,而不是?我很好奇。1 << 4
16
的 OpenJDK 代码包括以下行:java.util.HashMap
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
为什么在这里使用,而不是?我很好奇。1 << 4
16
用16而不是16来写并不能改变这里的行为。这样做是为了强调这个数字是二的幂,而不是一个完全任意的选择。因此,它提醒了尝试不同数字的开发人员,他们应该坚持这种模式(例如,use or ,not),这样他们就不会破坏所有依赖于它是二的幂的方法。上面有一条评论:1 << 4
1 << 3
1 << 5
20
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
无论 a 增长到多大,其表容量(阵列长度)都保持为 2 的幂。这允许使用快速按位 AND 操作 () 来选择存储对象的存储桶索引,如访问表的方法所示:java.util.HashMap
&
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { /// <-- bitwise 'AND' here
...
那里是表容量,并包装哈希值以适应该范围。n
(n - 1) & hash
哈希表有一个“buckets”数组(调用它们),其中每个存储桶存储零个或多个映射的键值对。HashMap
Node
每次我们或键值对时,我们都会计算密钥的哈希值。哈希是一些任意的(也许是巨大的)数字。然后,我们从哈希值中计算存储桶索引,以选择对象的存储位置。get
put
大于存储桶数的哈希值将被“包裹”以适合表。例如,如果表容量为 100 个存储桶,则哈希值 5、105、205 将全部存储在存储桶 5 中。把它想象成圆周围的度数,或者钟面上的小时。
(哈希也可以是负数。值 -95 可能对应于存储桶 5 或 95,具体取决于其实现方式。确切的公式并不重要,只要它在存储桶之间大致均匀地分配哈希值即可。
如果我们的表容量不是 2 的幂,则存储桶的公式将为 ,它使用模运算符来计算除以 后的余数,并用于固定负值。这将起作用,但速度较慢。n
Math.abs(hash % n)
n
abs
为什么速度较慢?想象一个十进制示例,其中有一些随机哈希值 12,459,217,任意表长度为 1,234。碰巧是753并不明显。这是一个很大的长分裂。但是,如果您的表长度是 10 的精确幂,则 结果只是最后 3 位数字:217。12459217 % 1234
12459217 % 1000
用二进制写成,2的幂是1,后跟一些0的数量,所以等效的技巧是可能的。例如,如果容量是十进制 16,则为二进制 10000。因此,是二进制 1111,并且仅保留与这些 1 相对应的哈希的最后一位,将其余部分归零。这也会使符号位归零,因此结果不能为负数。结果是从 0 到 n-1(包括 0 和 n-1)。这就是存储桶索引。n
n - 1
(n - 1) & hash
即使CPU的速度越来越快,它们的多媒体功能也得到了提高,整数除法仍然是你可以做的最昂贵的单指令操作之一。它可能比按位 AND 慢 50 倍,在频繁执行的循环中避免它可以带来真正的改进。
我无法读懂开发人员的想法,但是我们做了这样的事情来表明数字之间的关系。
比较一下:
int day = 86400;
与
int day = 60 * 60 * 24; // 86400
第二个例子清楚地显示了数字之间的关系,Java足够聪明,可以将其编译为常量。