缓存使用多个参数构建的对象 [英] Caching objects built with multiple parameters
问题描述
我有一个工厂创建类 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;
}
}
您实际上不应该将哈希码用作地图中的键.一个类的哈希码并非一定要保证该类的任何两个不相等的实例都不会相同.确实,您的哈希码方法肯定可以为两个不相等的实例产生相同的哈希码.您需要在MyClass
上实现equals
,以基于它们包含的String
和int
的相等性来检查MyClass
的两个实例是否相等.我还建议将s
和i
字段设置为final
,以便如果您将以这种方式使用它,则可以更强地保证每个MyClass
实例的不变性.
除此之外,我认为您实际上想要的是 interner ....也就是说,可以保证您最多只能存储给定MyClass
的1个实例.一次存储在内存中.正确的解决方案是Map<MyClass, MyClass>
...更具体地讲,如果有可能从多个线程调用getOrCreateMyClass
,则更确切地说是ConcurrentMap<MyClass, MyClass>
.现在,您确实需要创建一个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
实例使用该实例时,该实例将可以进行垃圾回收,从而帮助您避免不必要的实例浪费内存. /p>
如果您选择自己执行此操作,则需要使用ConcurrentMap
缓存并使用putIfAbsent
.我想您可以看看Guava强大的合作伙伴的实现方式,以供参考...但是,弱引用方法却要复杂得多.
I have a factory that creates objects of class MyClass, returning already generated ones when they exist. As I have the creation method (getOrCreateMyClass) taking multiple parameters, which is the best way to use a Map to store and retrieve the objects?
My current solution is the following, but it doesn't sound too clear to me. I use the hashCode method (slightly modified) of class MyClass to build an int based on the parameters of class MyClass, and I use it as the key of the 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;
}
}
You really shouldn't be using the hashcode as the key in your map. A class's hashcode is not intended to necessarily guarantee that it will not be the same for any two non-equal instances of that class. Indeed, your hashcode method could definitely produce the same hashcode for two non-equal instances. You do need to implement equals
on MyClass
to check that two instances of MyClass
are equal based on the equality of the String
and int
they contain. I'd also recommend making the s
and i
fields final
to provide a stronger guarantee of the immutability of each MyClass
instance if you're going to be using it this way.
Beyond that, I think what you actually want here is an interner.... that is, something to guarantee that you'll only ever store at most 1 instance of a given MyClass
in memory at a time. The correct solution to this is a Map<MyClass, MyClass>
... more specifically a ConcurrentMap<MyClass, MyClass>
if there's any chance of getOrCreateMyClass
being called from multiple threads. Now, you do need to create a new instance of MyClass
in order to check the cache when using this approach, but that's inevitable really... and it's not a big deal because MyClass
is easy to create.
Guava has something that does all the work for you here: its Interner interface and corresponding Interners factory/utility class. Here's how you might use it to implement getOrCreateMyClass
:
private static final Interner<MyClass> interner = Interners.newStrongInterner();
public static MyClass getOrCreateMyClass(String s, int i) {
return interner.intern(new MyClass(s, i));
}
Note that using a strong interner will, like your example code, keep each MyClass
it holds in memory as long as the interner is in memory, regardless of whether anything else in the program has a reference to a given instance. If you use newWeakInterner
instead, when there isn't anything elsewhere in your program using a given MyClass
instance, that instance will be eligible for garbage collection, helping you not waste memory with instances you don't need around.
If you choose to do this yourself, you'll want to use a ConcurrentMap
cache and use putIfAbsent
. You can take a look at the implementation of Guava's strong interner for reference I imagine... the weak reference approach is much more complicated though.
这篇关于缓存使用多个参数构建的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!