[英]Why use 1<<4 instead of 16?
java.util.HashMap
的 OpenJDK 代碼包括以下行:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
為什么在這里使用1 << 4
而不是16
? 我很好奇。
寫1 << 4
而不是 16 不會改變這里的行為。 這樣做是為了強調數字是 2 的冪,而不是完全任意的選擇。 因此,它提醒開發人員嘗試使用不同的數字,他們應該堅持這種模式(例如,使用1 << 3
或1 << 5
,而不是20
),這樣他們就不會破壞所有依賴它作為 2 的冪的方法. 上面有一條評論:
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
無論java.util.HashMap
增長到多大,它的表容量(數組長度)都保持為 2 的冪。 這允許使用快速按位與運算 ( &
) 來選擇存儲對象的存儲桶索引,如訪問表的方法中所示:
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
包裝了散列值以適應該范圍。
哈希表有一個“桶”數組( HashMap
稱它們為Node
),其中每個桶存儲零個或多個映射的鍵值對。
每次我們get
或put
一個鍵值對時,我們都會計算該鍵的哈希值。 哈希是一些任意(可能很大)的數字。 然后我們從散列中計算一個桶索引,以選擇對象的存儲位置。
大於桶數的哈希值被“環繞”以適應表格。 例如,表容量為 100 個桶,哈希值 5、105、205 將全部存儲在桶 5 中。可以將其想象為圓周上的度數,或鍾面上的小時數。
(哈希值也可以是負數。-95 的值可能對應於存儲桶 5 或 95,具體取決於它是如何實現的。確切的公式並不重要,只要它在存儲桶之間大致均勻地分布哈希即可。)
如果我們的表容量n
不是 2 的冪,則存儲桶的公式將是Math.abs(hash % n)
,它使用模運算符計算除以n
后的余數,並使用abs
來修復負值。 這會起作用,但會慢一些。
為什么慢? 想象一個十進制示例,其中您有一些隨機哈希值 12,459,217,並且任意表長度為 1,234。 12459217 % 1234
恰好是 753 並不明顯。這是一個很長的除法。 但是,如果您的表格長度是 10 的精確冪,那么12459217 % 1000
的結果就是最后 3 位數字:217。
用binary編寫,2 的冪是 1 后跟一些 0,因此等效的技巧是可能的。 例如,如果容量n
是十進制 16,即二進制 10000。因此, n - 1
是二進制 1111,並且(n - 1) & hash
僅保留與這些 1 對應的哈希的最后一位,其余的歸零。 這也將符號位清零,因此結果不能為負。 結果是從 0 到 n-1(含)。 那就是桶索引。
即使 CPU 變得更快並且它們的多媒體功能得到了改進,整數除法仍然是您可以執行的最昂貴的單指令操作之一。 它可能比按位 AND 慢 50 倍,並且在頻繁執行的循環中避免它可以帶來真正的改進。
我無法讀懂開發人員的想法,但我們這樣做是為了表明數字之間的關系。
比較一下:
int day = 86400;
對比
int day = 60 * 60 * 24; // 86400
第二個示例清楚地顯示了數字之間的關系,Java 足夠聰明,可以將其編譯為常量。
我認為原因是開發人員可以很容易地更改值(根據 JavaDoc '/* 默認初始容量 - 必須是 2 的冪。*/')例如1 << 5
或1 << 3
和他不需要做任何計算。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.