2

TL;博士:在Java中,這是更好的,容器對象的重用或每次創建對象,讓垃圾回收器做Java:重用vs重新分配對容器對象的引用?

我處理與Java數據量巨大的工作,我有以下地方頻繁代碼結構的類型: -

版本1:

for(...){//outer loop 
    HashSet<Integer> test = new HashSet<>(); //Some container 
    for(...){ 
     //Inner loop working on the above container Data Structure 
    } 
    //More operation on the container defined above 
}//Outer loop ends 

在這裏,我每次都在一個循環中分配新的內存和分配空MEMOR之前做內/外循環的一些操作再次。

現在我擔心Java中的內存泄漏。我知道,Java有一個相當不錯的垃圾收集器,但不是依靠我應該修改我的代碼如下: -

版本2:

HashSet<Integer> test = null; 
for(...){//outer loop 
    if(test == null){ 
     test = new HashSet<>(); //Some container 
    }else{ 
     test.clear() 
    } 
    for(...){ 
     //Inner loop working on the above container Data Structure 
    } 
    //More operation on the container defined above 
}//Outer loop ends 

我有三個問題: -

  1. 哪個表現會更好,還是沒有確切的答案。
  2. 第二個版本會有更多的時間複雜度嗎?換句話說,複雜度爲O(n)的clear()函數O(1)。我在javadoc中沒有任何東西。
  3. 這種模式很常見,哪個版本更值得推薦?
+1

'clear'可能會稍微快一點(上次我檢查了10/15%)。最好的方法是用你的數據測試兩種方法。在正常計劃中,這不會成爲瓶頸。 – assylias

+0

@assylias我能想到的一個原因是,內存必須每次調整大小如果我每次都分配新內存。但是空間複雜性呢?如果速度不是問題,將會對內存佔用產生重大影響? –

+0

@TagirValeev對不起,我在那裏很不在乎。我打算空檢查,如果容器從來沒有被初始化,我會給我的內存,否則我只會清理並重用我得到的空間 –

回答

5

我認爲最好使用第一種方法。請注意,HashSet.clear永遠不會縮小哈希表的大小。因此,如果外循環的第一次迭代將許多元素添加到集合中,那麼哈希表將變得相當大,但是在後續迭代中,即使不需要收縮也不需要太多的空間。

也是第一個版本使得進一步的重構更容易:您可能稍後想要將整個內部循環放入單獨的方法中。使用第一個版本,您可以將其與HashSet一起移動。

最後要注意的是,對於垃圾收集來說,管理短期對象通常更容易。如果您的HashSet是長期存在的,它可能會轉移到舊一代,並且只在整個GC期間移除。

+0

謝謝,重構很有趣。但關於大小,明確之後,下一個元素將被覆蓋在已擴展的空間中。這將節省時間,因爲在容器中重新調整大小的代價非常高,並且最大大小將是最大值(所有迭代) –

+0

@MangatRaiModi,如果您可以提前估計需要多少元素,只需將參數傳遞給'HashSet'構造函數即可避免換湯不換藥。 –

+0

我想過了,但是由於元素被添加到Hashset中受到各種條件的影響,所以它非常困難。根據估計過度提交是否是一種糟糕的做法? –

0

2版更好 但它會採取更多的時間,但內存性能點點會好

+0

有些解釋真的會有幫助! –

+1

我不認爲版本2會更快* madamji * :)。事實上,假設一個版本比另一個版本更快是不正確的。根據JVM(JIT),底層系統架構,一個版本可能在一個系統上更快,但在另一個系統上速度更慢) – TheLostMind

+0

@TheLostMind沮喪的印度人發現:p [Off Topic] –

3

我建議你堅持到第一個變種。這背後的主要原因是保持HashSet變量的範圍儘可能小。這樣,您確實可以確保在迭代結束後它有資格進行垃圾回收。提升它的範圍可能會導致其他問題 - 該參考可以稍後用於實際改變對象的狀態。

此外,如果您在循環內部或外部創建實例,大多數現代Java編譯器將生成相同的字節碼。

+0

*大多數現代Java編譯器將如果在循環內部或外部創建實例,則會生成相同的字節碼。 +1這一行:) – TheLostMind

3

我認爲每次創建一個新的HashSet會更簡單,並且稍後可能不太容易出現重構錯誤。除非你有充分的理由來重新使用HashSet(垃圾收集暫停對你來說是一個問題,並且分析表明這部分代碼是原因) - 我會盡可能保持簡單並堅持1.重視可維護性,應該避免使用Premature Optimization

1

哪一個更快?其實答案可能因各種因素而異。

版本-1優點:

  1. 在處理器級別的預測分支可能使這個速度更快。
  2. 實例的範圍僅限於第一個循環。如果引用不能轉義,JIT實際上可能會編譯你的方法。 GC的工作可能會更容易, 。

版本-2:

  1. 更少的時間在創造新的容器(坦白地說,這是不是太多)。
  2. clear()O(n)
  3. 轉義引用可能會阻止JIT進行某些優化。

哪一個可以選擇? 幾次測量兩個版本的性能。那麼如果你發現有顯着差異,改變你的代碼,如果沒有,不要做任何事情:)

+0

清楚是O(n)是非常糟糕的,我認爲Java會有一些標誌,如:在使用/轉儲。我想我會更好地堅持版本1。謝謝您的幫助。 –

+0

@MangatRaiModi - 是的,不幸的是,對於'clear()',你將不得不將每個單元格放在*容器*中,並將其設置爲'null'(如果你泄漏了引用?)。所以它是'O(n)'。我有一種感覺,版本1會更快:) – TheLostMind

+1

糟糕,沒有想過泄漏的引用,我只想到原始數據類型。所以,它必須是O(n)。 –

0

這要看。

回收物品可用於緊密環路以消除GC壓力。特別是當年輕一代的對象太大或循環運行時間足夠長時間以使其被終身使用時。

但在您的特定示例中,它可能沒有多大幫助,因爲哈希集仍包含節點對象,這些節點對象將在插入時創建併成爲符合清除GC的條件。另一方面,如果你把太多的物品放進集合中,它的內部Object[]陣列必須多次調整大小,並且對於年輕一代來說變得太大,那麼回收集合可能是有用的。但是在那種情況下,你應該預先設定這個設定。

此外,僅在代碼塊期間生存的對象可以通過escape analysis有資格進行對象分解/堆棧分配。它們的壽命越短,觸及這些對象的代碼路徑越不復雜,EA越有可能成功。

最後,它並不重要,直到這個方法實際上成爲應用程序中的分配熱點,在這種情況下,它將顯示在分析器結果中,並且您可以相應地採取行動。