2010-11-14 36 views
0

我有一個工廠創建類MyClass的對象,當它們存在時返回已生成的對象。由於我有創建方法(getOrCreateMyClass)採用多個參數,這是使用Map存儲和檢索對象的最佳方法?使用多個參數緩存對象

我目前的解決方案如下,但對我來說聽起來不太清楚。 我使用類MyClass的hashCode方法(稍作修改)根據類MyClass的參數構建int,並將其用作Map的關鍵字。

import java.util.HashMap; 
import java.util.Map; 

public class MyClassFactory { 

    static Map<Integer, MyClass> cache = new HashMap<Integer, MyClass>(); 

    private static class MyClass { 
     private String s; 
     private int i; 

     public MyClass(String s, int i) { 
     } 

     public static int getHashCode(String s, int i) { 
      final int prime = 31; 
      int result = 1; 
      result = prime * result + i; 
      result = prime * result + ((s == null) ? 0 : s.hashCode()); 
      return result; 
     } 

     @Override 
     public int hashCode() { 
      return getHashCode(this.s, this.i); 
     } 

    } 


    public static MyClass getOrCreateMyClass(String s, int i) { 
     int hashCode = MyClass.getHashCode(s, i); 
     MyClass a = cache.get(hashCode); 
     if (a == null) { 
      a = new MyClass(s, i); 
      cache.put(hashCode , a); 

     } 
     return a; 
    } 

} 
+0

看起來不錯。你爲什麼不喜歡它? – skaffman 2010-11-14 20:57:19

+0

嗯,它似乎沒有太多的面向對象,我目前有一個像這樣的工廠未知的錯誤,但也許問題不在於該代碼... – cdarwin 2010-11-14 20:59:32

+0

對不起,我忘了一行! ! (這是示例代碼),修復cache.put – cdarwin 2010-11-14 21:21:39

回答

2

你真的不應該使用散列碼作爲你的地圖中的鍵。一個類的哈希碼並不是要保證它對於該類的任何兩個不等於的實例都是不一樣的。事實上,你的散列碼方法可以爲兩個不相等的實例產生相同的散列碼。您需要需要來執行equalsMyClass以檢查MyClass的兩個實例是否相等,這是基於它們包含的Stringint的等同性。如果您打算以這種方式使用它,我還建議使si字段final爲每個MyClass實例的不變性提供更強有力的保證。

除此之外,我想到你居然想在這裏什麼是interner ....也就是說,東西保證你會永遠只能存儲一個給定的MyClass最多1個實例在內存中的時間。對此的正確解決方案是Map<MyClass, MyClass> ...更具體地說,如果有多個線程調用getOrCreateMyClass的機會,更具體地說是ConcurrentMap<MyClass, MyClass>。現在,您需要創建一個MyClass的新實例,以便在使用此方法時檢查緩存,但這確實不可避免...並且這不是什麼大問題,因爲MyClass很容易創建。

Guava有一些功能可以爲您完成所有工作:Interner接口和相應的Interners工廠/實用程序類。下面是如何使用它來實現getOrCreateMyClass

private static final Interner<MyClass> interner = Interners.newStrongInterner(); 

public static MyClass getOrCreateMyClass(String s, int i) { 
    return interner.intern(new MyClass(s, i)); 
} 

注意,使用強interner會,喜歡你的示例代碼,保存在內存中,只要interner在內存中,不管什麼保存每個MyClass其他程序中有一個給定實例的引用。如果您使用newWeakInterner來替代,那麼當您的程序中沒有其他地方使用給定的MyClass實例時,該實例將有資格進行垃圾回收,這樣可以幫助您避免浪費內存,避免出現不需要的情況。

如果您自己選擇這樣做,則需要使用ConcurrentMap緩存並使用putIfAbsent。你可以看看Guava強大的interner的實現,以供我參考......儘管弱參考方法要複雜得多。

+0

+1:如果MyClass確實與創建對象一樣便宜,並且如果OP願意將Guava添加爲依賴,這當然聽起來像更好的解決方案。 – 2010-11-14 22:12:16

3

您的getOrCreateMyClass似乎並沒有添加到緩存中,如果它創建。

我認爲當hashcodes發生衝突時,這也不會正確執行。相同的哈希碼不意味着相同的對象。這可能是您在評論中提到的錯誤的來源。

你可能會考慮實際equalshashCode方法創建一個通用的Pair類和使用Pair<String, Integer>類作爲緩存的地圖鍵。

編輯:

額外的內存消耗通過存儲既是Pair<String, Integer>鍵和MyClass值的問題可能是最好的通過使Pair<String, Integer>MyClass一個領域,從而具有隻有一個引用這個處理目的。

儘管如此,您可能不得不擔心線程問題,這些問題似乎尚未解決,哪些可能是錯誤的另一個來源。

是否它實際上是一個好主意取決於MyClass的創建是否比創建映射密鑰昂貴得多。

另一個編輯:

ColinD的回答也是合理的(我已經upvoted它),只要MyClass建設也不貴。

可能值得考慮的另一種方法是使用嵌套映射Map<String, Map<Integer, MyClass>>,這將需要兩級查找並使緩存更新複雜一點。

+2

+1爲碰撞評論。 我建議不要使用Pair作爲關鍵字,因爲您將在MyClass中保留* 2個對每個對象的引用,從而使緩存消耗更多內存。 – Jorn 2010-11-14 21:24:15

+0

我必須創建數以千計的MyClass對象,所以我希望避免每次創建新的Pair對象。碰撞評論真的很有趣 – cdarwin 2010-11-14 21:27:32

+0

我對內存消耗的影響並不興奮。我想不出一種替代方案能夠在不重新引入哈希碼碰撞風險的情況下減少內存消耗。 – 2010-11-14 21:33:25