2010-11-14 65 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