缓存使用多个参数构建的对象

时间:2010-11-14 20:55:16

标签: java caching map factory

我有一个工厂可以创建类 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;
    }

}

2 个答案:

答案 0 :(得分:3)

如果getOrCreateMyClass创建,则Pair似乎不会添加到缓存中。

我认为当hashcode发生碰撞时,这也无法正常执行。相同的哈希码并不意味着相等的对象。这可能是您在评论中提到的错误的来源。

您可以考虑使用实际equalshashCode方法创建通用Pair<String, Integer>类,并使用Pair<String, Integer>类作为缓存的地图键。

修改

通过将MyClass键和[{1}}值存储在一起可能最好通过将Pair<String, Integer>放入MyClass的字段来处理额外内存消耗的问题因此只有一个参考这个对象。

尽管如此,您可能不得不担心线程问题似乎尚未解决,而且可能是其他错误来源。

它是否真的是一个好主意取决于MyClass的创建是否比创建地图密钥昂贵得多。

另一个编辑:

只要MyClass的构建并不昂贵,ColinD的答案也是合理的(我已经投了赞成票)。

可能值得考虑的另一种方法是使用嵌套映射Map<String, Map<Integer, MyClass>>,这需要两阶段查找并使缓存更新一点。

答案 1 :(得分:2)

你真的不应该使用哈希码作为地图中的键。类的哈希码并不一定要保证它对于该类的任何两个不相等的实例都不相同。实际上,您的hashcode方法肯定可以为两个不相等的实例生成相同的哈希码。您需要equals上实施MyClass,以根据MyClass和{{的相等性检查String的两个实例是否相等1}}它们包含。我还建议制作ints字段i,以便为每个final实例的不变性提供更强大的保证,如果您要使用它方式。

除此之外,我认为你真正想要的是一个 interner ....也就是说,保证你最多只能存储给定{{1}的一个实例一次在内存中。对此的正确解决方案是MyClass ...更具体地说是MyClass,如果有可能从多个线程调用Map<MyClass, MyClass>。现在,你需要创建一个ConcurrentMap<MyClass, MyClass>的新实例,以便在使用这种方法时检查缓存,但这确实是不可避免的......而且这并不是什么大问题,因为getOrCreateMyClass很容易创建

Guava在这里可以为您完成所有工作:它的Interner接口和相应的Interners工厂/实用程序类。以下是您可以使用它来实现MyClass

的方法
MyClass

请注意,使用强大的内部工具,就像您的示例代码一样,只要内部存储器在内存中,它就会保留在内存中的每个getOrCreateMyClass,无论程序中是否有任何其他内容引用给定的内容实例。如果您使用private static final Interner<MyClass> interner = Interners.newStrongInterner(); public static MyClass getOrCreateMyClass(String s, int i) { return interner.intern(new MyClass(s, i)); } 代替,当您的程序中没有其他任何内容使用给定的MyClass实例时,该实例将有资格进行垃圾回收,帮助您不浪费内存而不使用实例需要周围。

如果您选择自行执行此操作,则需要使用newWeakInterner缓存并使用MyClass。你可以看一下Guava强大的interner的实现,以供参考我想象......弱参考方法虽然复杂得多。