使用Guava CacheBuilder或MapMaker实现弱引用/软引用缓存

12
我不太习惯在Java中处理软引用和弱引用,但我理解原则,因为我习惯于处理类似Gemfire的数据网格,当内存已满时,提供了溢出到HDD功能,可能使用软引用或类似的东西。

我不理解Guava为什么会提供将键设为软/弱引用以及将值设为软/弱引用的方法。

我只是想知道创建具有非软/弱值的软键的意义何在?我的意思是,当软引用开始被收集时,我们无法通过其键再找到该条目,那么为什么希望值留在映射中呢?

有人能给出以下用例吗:

  • 弱键/软值
  • 弱键/普通值
  • 软键/弱值
  • 软键/普通值

谢谢。


编辑 我不确定我的问题是否足够明确,所以我想知道:

  • 当键(弱引用/软引用)被收集时,值(非弱引用/软引用)会发生什么
  • 当值(弱引用/软引用)被收集时,键会发生什么
  • 带有缺失部分(键或值)的条目是否保留在缓存中?
  • 是否存在任何用例,您希望这样的条目留在缓存中。

编辑: 正如在Kevin Bourillon的答案中讨论的那样,最后我想我明白了为什么使用软键没有意义。 原因如下:

static class KeyHolder {
    final private String key;
    public KeyHolder(String key) {
        this.key = key;
    }
    public String getKey() {
        return key;
    }
    @Override
    public boolean equals(Object o) {
        KeyHolder that = (KeyHolder)o;
        boolean equality = this.getKey().equals(that.getKey());
        return equality;
    }
    @Override
    public int hashCode() {
        return key != null ? key.hashCode() : 0;
    }
    @Override
    public String toString() {
        return "KeyHolder{" +
                "key='" + key + '\'' +
                '}';
    }
}

public static void main(String[] args) {
    System.out.println("TESTING WEAK KEYS");
    testMap( new MapMaker().weakKeys().<KeyHolder,String>makeMap() );


    System.out.println("\n\n");
    System.out.println("TESTING SOFT KEYS");
    testMap(new MapMaker().softKeys().<KeyHolder, String>makeMap());


    System.out.println("\n\n");
    System.out.println("TESTING SOFT REFERENCES");
    KeyHolder key1 = new KeyHolder("toto");
    KeyHolder key2 = new KeyHolder("toto");
    SoftReference<KeyHolder> softRef1 = new SoftReference<KeyHolder>(key1);
    SoftReference<KeyHolder> softRef2 = new SoftReference<KeyHolder>(key2);
    System.out.println( "equals keys? " + key1.equals(key2) );
    System.out.println( "equals ref? " + softRef1.equals(softRef2) );
}

private static void testMap(Map<KeyHolder,String> map) {
    KeyHolder strongRefKey = new KeyHolder("toto");
    KeyHolder noStrongRefKey = new KeyHolder("tata");
    map.put(strongRefKey,"strongRef");
    map.put(noStrongRefKey,"noStrongRefKey");
    // we replace the strong reference by another key instance which is equals
    // this could happen for exemple in case of serialization/deserialization of the key
    noStrongRefKey = new KeyHolder("tata");
    System.gc();
    System.out.println( "strongRefKey = " + map.get(strongRefKey) );
    System.out.println( "noStrongRefKey = " + map.get(noStrongRefKey) );
    System.out.println( "keyset = " + map.keySet() );
}
该代码生成以下输出:
TESTING WEAK KEYS
strongRefKey = strongRef
noStrongRefKey = null
keyset = [KeyHolder{key='toto'}]



TESTING SOFT KEYS
strongRefKey = strongRef
noStrongRefKey = null
keyset = [KeyHolder{key='tata'}, KeyHolder{key='toto'}]



TESTING SOFT REFERENCES
toto == toto -> true
equals keys? true
equals ref? false

正如您所看到的,使用(不建议使用的)软键映射,包含“tata”键的KeyHolder仍然存在于映射中。 但请注意,我仍然无法通过新创建的键“new KeyHolder(“tata”);”找到我的条目。 这是因为我的键具有有意义的相等性,但它们周围的引用包装器并不相等,因为它们的相等方法在Guava中没有被覆盖! 在这种情况下,是的,softKeys没有任何意义,因为您绝对需要保留对该键的标识引用才能检索它。

2个回答

15

softKeys方法毫无意义,因此我们已经将其删除。假设值实例在缓存之外也不能被访问,则softValues是使用软引用的唯一方法。

然后,使用weakKeys的基本原则就是是否需要键的标识相等性。如果键重写了equals并且您需要那种相等性行为,则无法使用它。如果您需要标识,则weakKeys是获取标识的方法,并且这也是有意义的,因为一旦所有对键的其他引用都已被垃圾回收,将无法查找该条目,因此最好将其删除。

我其实不太清楚何时使用weakValues是有用的,我想去了解一下。这可能是weakKeys不是一个选项的情况(比如使用Integer作为键),并且通常情况下该值通过其他方式被强引用,例如某种会话对象,但是当对象消失时,就表示没有人会再从缓存中查找这个值。虽然这种情况有点牵强。


2
如果您使用弱键,并且在缓存之外不再具有键引用,则键将被收集,但是值会发生什么?它们是否保留在缓存中? - Sebastien Lorber
1
当你说softKeys没有意义时,我不确定这是否正确。如果您有重建新键的手段:即使您没有任何身份引用,您可能希望将键保留在缓存中,因为您可以再次创建适当的键,对吧?那么克隆/序列化/反序列化密钥呢?你明白我的意思吗? - Sebastien Lorber
1
在几乎任何softKeys看起来能够工作的情况下,weakKeys都能更好地发挥作用。一旦键不再强烈可达,weakKeys就允许它被GC回收。softKeys可能会将其保留任意长时间--但是为了什么目的呢?因为如前所述,该键不再强烈可达,所以没有人可以查找该条目!因此,我们只剩下像“哦,但是我所有其他对该键的引用肯定也是软引用”,或者“哦,但是我有时会遍历asMap()条目并为某些原因恢复某些键”这样的边缘情况...这些情况实际上从未出现过。 - Kevin Bourrillion
4
@KevinBourrillion,请查看我的编辑。正如我所说,一个键可以被序列化,反序列化例如。如果Guava覆盖了SoftReference的equals方法,那么软键会有意义。我已经看到Guava使用了“等价机制”,我认为对于软引用,defaultEquivalence不应该是identity而是equals,以处理这种情况。然后,使用softKeys就有意义了,因为你将能够通过键的副本检索映射值。你觉得呢? - Sebastien Lorber
@KevinBourrillion 如果在API文档或至少在源代码中提到了有关删除softKeys()的解释,例如在setKeyStrength(Strength)中,那就太好了。 - gouessej
显示剩余4条评论

2
非Guava WeakHashMap基于弱键,并在键不再使用时丢弃整个条目。因此,当值不再留在映射中时,就没有“值留在映射中”的情况了。这是我从这种数据结构直观上期望的行为方式,我强烈怀疑Guava也会以同样的方式处理已收集的软引用。
请注意,Java Collections库仅提供弱键/正常值映射,而不提供弱键/软值或弱键/弱值,因为用户可以自己包装值(但不能像键一样进行包装,因为哈希映射中的查找实际键和对键的弱引用不应该被认为是相同的)。
我曾经使用WeakHashMap将某个机制中可能变得过时的对象与与该机制无关的信息相关联(确切地说,是移动的球,可能会因与其最近运动有关的某些碰撞而被摧毁)。这些值是专门为该映射创建的,而且没有在其他地方引用(除了只能在其键仍然存在时调用的方法中本地引用)。因此,没有必要使用除正常引用之外的任何东西来引用值,因为当键变得过时时,值将变得不可引用并可垃圾回收。

1
请注意,在使用弱键时有一个警告:“警告:当使用此方法[weakKeys()]时,生成的缓存将使用身份(==)比较来确定键的相等性。” - thSoft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接