2017-07-27 79 views
14

我有點難以理解java-9的實現細節ImmutableCollections.SetN;特別是爲什麼需要增加兩次內部數組。ImmutableCollections SetN實現細節

假如你這樣做:

Set.of(1,2,3,4) // 4 elements, but internal array is 8 

更確切地說我完全理解爲什麼這是在HashMap的情況下完成的(雙膨脹) - 在這裏你永遠(幾乎)希望load_factor是一個。例如,值爲!=1可以改善搜索時間,因爲條目可以更好地分散到存儲區中。

但在不可變的情況下設置 - 我真的不知道。特別是因爲內部數組的索引被選中。

讓我提供一些細節。首先如何搜索索引:

int idx = Math.floorMod(pe.hashCode()^SALT, elements.length); 

pe是我們在集合中的實際值。 SALT只是在啓動時產生的32位,每JVM一次(如果您需要,這是實際的隨機化)。我們的例子中的elements.length8(4個元素,但這裏是8個 - 大小的兩倍)。

該表達式類似於負安全模運算。請注意,在選擇存儲桶時,在HashMap中例如((n - 1) & hash)完成了相同的邏輯。

因此,如果我們的情況爲elements.length is 8,那麼此表達式將返回小於8的任何正數值。(0, 1, 2, 3, 4, 5, 6, 7)

現在該方法的其餘部分:

while (true) { 
     E ee = elements[idx]; 
     if (ee == null) { 
      return -idx - 1; 
     } else if (pe.equals(ee)) { 
      return idx; 
     } else if (++idx == elements.length) { 
      idx = 0; 
     } 
    } 

讓我們來分析一下:

if (ee == null) { 
    return -idx - 1; 

這是好事,這意味着數組中的當前插槽是空的 - 我們可以把我們的價值在那裏。

} else if (pe.equals(ee)) { 
    return idx; 

這是壞的 - 插槽被佔用,已經存在的條目等於我們想要放置的條目。 Set s不能有重複的元素 - 所以後面拋出一個異常。

else if (++idx == elements.length) { 
     idx = 0; 
} 

這意味着這個槽被佔用(散列衝突),但是元素不相等。在HashMap這個條目將被放在與LinkedNodeTreeNode相同的桶 - 但在這裏並不是這樣。

因此index增加,並嘗試下一個位置(小的警告,它在它到達最後位置時以循環方式移動)。

這裏有一個問題:如果在搜索索引時沒有什麼太花哨的東西(除非我錯過了某些東西),爲什麼需要將數組放大兩倍?爲什麼功能不這樣寫的:

int idx = Math.floorMod(pe.hashCode()^SALT, input.length); 

// notice the diff elements.length (8) and not input.length (4) 
+0

@GhostCat https://github.com/netroby/jdk9-dev/blob/master/jdk/src/java.base/share/classes/java/util/ImmutableCollections.java#L446-L547('probe '方法) –

+0

@VinceEmigh要完成其他討論:通常事情不是黑色或白色。我看到很多6digits的人提供的答案確實提供了更少的內容或更多的「fuziness」,但在幾個小時內獲得了大量的選票。我看到好的內容坐了很長時間......並沒有發生任何事情。你會看到 - 零其他答案,甚至沒有評論這個問題。所以也許一個可能的解釋清單並不能解釋一個很好的答案 - 但它可以提供思考的食物。除此之外:這不是什麼投票的目的?我會以直接反饋的形式記錄下來...以及:我嘗試做 – GhostCat

+0

@VinceEmigh在下一次提出更好的建議。 – GhostCat

回答

14

目前執行的SetN是一個相當簡單的封閉散列方案,而不是由HashMap使用單獨的鏈接方法。 (「封閉散列」也令人混淆稱爲「open addressing」。)在一個封閉的散列方案,元件被存儲,而不是被存儲在從每個表槽,這是連接在一起的元件的列表,或樹中的表本身,單獨的鏈接。

這意味着,如果兩種不同元素散列到相同的表槽,該碰撞需要通過找到另一個插槽爲元件中的一個來解決。當前SetN執行這一使用線性探測,其中表時隙被順序選中(在端部纏繞),直到開口槽被發現解決。

如果你想存儲N元素,他們肯定會適合一個大小N表。您可以隨時查找集合中的任何元素,但您可能需要探查幾個(或多個)連續的表格插槽才能找到它,因爲會有很多衝突。但是如果該組被探測到不是一個成員的對象,那麼線性探測必須在每個表槽中檢查,然後才能確定該對象不是成員。使用完整表格,大多數探測操作將降級到O(N)時間,而大多數基於散列的方法的目標是操作爲O(1)次。

因此,我們有一類時空權衡。如果我們把桌子放大一點,桌子上就會出現空插槽。存儲物品時,碰撞應該更少,線性探測會更快地找到空槽。彼此相鄰的全部插槽的集羣將更小。對於非成員的探測將會更快地進行,因爲他們更可能在線性探測時更快地遇到空插槽 - 可能在根本不需要再探查之後。

在造就的實施,我們跑了一堆用不同的膨脹係數基準。 (我在代碼中使用了術語EXPAND_FACTOR,而大多數文獻使用加載因子。原因是擴展因子是HashMap中使用的加載因子的倒數,並且對這兩種含義使用「加載因子」令人困惑。)當擴展係數接近1.0時,探測性能非常緩慢,正如預期的那樣。隨着擴展因素的增加,它大大改善。到3.0或4.0時,這種改進實際上已經趨於平緩。我們選擇了2.0,因爲它獲得了大部分性能提升(接近O(1)時間),與HashSet相比,節省了很多空間。 (對不起,我們沒有在任何地方發表這些基準數字)。

當然,所有這些都是實現細節,並可以從一個版本更改到下一個,因爲我們找到更好的方法來優化系統。我確信有辦法改進目前的實施。 (幸運的是,我們不必擔心preserving iteration order我們這樣做的時候。)

與客座率開放尋址和性能折衷一個很好的討論可以在

塞奇威克,羅伯特的第3.4節中找到和凱文韋恩。 算法,第四版。 Addison-Wesley,2011年。

在線預訂網站是here但請注意印刷版有更多的細節。

+1

在使用的算法的代碼中添加一個簡單的評論是否有意義?那條單線可以爲我節省一段時間,而我正在挖掘* why * s。順便說一句HashMap有一堆這些意見在實施細節... – Eugene

+1

當你說搜索我立即做了一個脾氣暴躁的臉。我很明顯地衝動着這個問題,並沒有考慮其他方面。謝謝 – Eugene

+4

當我們處於這種狀態時......是否有意讓這些不可變集合沒有優化的'forEach(...)'或'spliterator()'方法? – Holger