【问题标题】:Java: need advise about WeakHashMapJava:需要有关 WeakHashMap 的建议
【发布时间】:2010-12-04 18:02:46
【问题描述】:

我想我是另一个试图用 Wea​​kHashMap 制作某种缓存的人。我需要一些帮助。

我有一堆 TrackData 对象,其中包含有关音轨的信息。然后是Track 对象,它们保持对内部TrackData 的引用。多个轨道可以指向同一个TrackData。然后我有TrackDataCache 类,看起来像这样:

public class TrackDataCache {
private static TrackDataCache instance = new TrackDataCache();

public static TrackDataCache getInstance() {
    return instance;
}

private WeakHashMap<TrackData, WeakReference<TrackData>> cache = new WeakHashMap<TrackData, WeakReference<TrackData>>();

public void cache(Track track) {
    TrackData key = track.getTrackData();
    WeakReference<TrackData> trackData = cache.get(key);
    if (trackData == null) {
        cache.put(key, new WeakReference<TrackData>(key));
    } else {
        track.setTrackData(trackData.get());
    }
}
}

因此,当我加载曲目时,我调用TrackDataCache.cache(),如果之前未加载其曲目数据,则将其缓存或替换为缓存副本,否则(TrackData 覆盖 equals() 方法来检查位置和子歌曲索引)。我想使用弱引用,这样我在删除 Tracks 时就不需要关心了。

我想问在 WeakHashMap 中保持对键的弱引用是否是一种好的做法,如果不是,我应该如何解决这个问题?我需要弱引用和恒定时间检索缓存值。我正在考虑复制 WeakHashMap 代码并将 getEntry() 方法公开,这解决了问题,但这是一个糟糕的 hack :(

附言。我知道 apache 或 google 集合可能有这样的东西,但我真的不想添加 2Mb 依赖项。

【问题讨论】:

标签: java caching weakhashmap


【解决方案1】:

我建议将WeakReferences 替换为SoftReferences

仅由WeakReference 引用的任何对象都是垃圾收集器每一轮的目标。这意味着即使还有足够的可用内存,也可以清除缓存。

如果您将WeakReference 替换为SoftReference,那么您声明:仅在绝对没有可用内存可分配时删除引用的对象。

java 中没有现成的SoftHashMap 实现。番石榴中有一个很好的例子 - MapMaker。值得在生产环境代码中使用这个经过良好测试和验证的代码,而不是提供您自己的质量肯定较低的实现。它还具有惊人的“自清洁”机制:

  1. 您可以指定cache max size

    随着地图大小的增长接近 最大值,地图将驱逐条目 不太可能再次使用。 例如,地图可能会驱逐一个 条目,因为它没有被使用 最近或经常。

  2. 您可以使用expireAfterWriteexpireAfterAccess 方法指定映射条目的过期时间。

我也发现你的缓存设计不是很方便。正如我从您的代码 sn-p 中了解的那样,从一开始您的 Tracks 就强烈引用了他们的 TrackData 并且您在这些情况下构建缓存。但是从某个时刻开始,您想使用缓存来检索数据,因此您必须以其他方式创建新的Tracks,因为从那个时刻您想要使用缓存但不使用强引用。

不同的Tracks 可以有相同的TrackData,所以我们不能使用Track 作为键。所以,我会采用下一种方法:

  1. 引入中间ids级别,基于Map&lt;Integer, TrackData&gt;的软值和定义的自清理策略(基于MapMaker)进行缓存;
  2. 将关系 Track --&gt; TrackData 更改为 Track --&gt; Id (int)。缓存Id --&gt; TrackData

【讨论】:

    【解决方案2】:

    TrackData 可以被Track 的多个实例共享。我们需要有一个不需要TrackData 的密钥系统来为多个Track 获取相同的实例。

     public class Track [
    
       @Override
       public int hashcode() {
         ... make hashcode that will be the same for
         ... tracks sharing the same track data.
       }
    
       @Override
       public boolean equals() {
         ... ensure that if A.hashcode == B.hashcode then A.equals(B)
       }
    
     }
    
     public class TrackDataManager {
    
       private WeakHashMap<Track,TrackData> cache = new WeakHashMap<Track,TrackData>();
    
       public TrackData getTrackData(Track track) {
    
         // Track.hashcode()/equals() ensures two tracks that
         // share track data will get the same object back
         TrackData data = cache.get(track);
    
         if (data == null) {
    
           data = constructDataFromTrackFile(track);
    
           cache.put(track, data);
    
         }
    
         return data;
    
       }
    
       private TrackData constructDataFromTrackFile(Track track) {
         ... read data from file and create that object.
       }
    
     }
    

    如果TrackData 对象的构造总是作为读取文件的一部分发生,但创建的实例被丢弃以支持共享实例,我会这样建模:

     public class TrackData {
    
       @Override
       public int hashcode() {
         ... make hashcode that will be the same for same track data.
       }
    
       @Override
       public boolean equals() {
         ... ensure that if A.hashcode == B.hashcode then A.equals(B)
       }
    
     }
    
     public class TrackDataCache {
    
       private WeakHashMap<Integer,TrackData> cache = new WeakHashMap<Integer,TrackData>();
    
       public TrackData getTrackData(Track track) {
    
         // cache contains shared TrackData instances, we may throw away
         // the Track instance in favour of the shared one.
    
         Integer key = track.getTrackData().hashcode();
    
         TrackData data = cache.get(key);
    
         if (data == null) {
    
           cache.put(key, track.getTrackData());
           data = track.getTrackData();
    
         } else {
    
           // ensure we're using the shared instance, not the local one.
           // deliberate object reference comparison  
           if (data != track.getTrackData()) {
             track.setTrackData(data);
           } 
    
         }
    
         return data;
    
       }
    
     }
    

    请注意,只要有Track 对象保持对TrackData 的引用,WeakHashMap 在这两种解决方案中的任何一种中都不会做任何事情。这可以通过在Track 中设置WeakReference 来解决 - 但这也意味着您最终可能没有任何TrackData,并且需要从文件中读取它,在这种情况下,第一个解决方案比第二个解决方案更好建模.

    【讨论】:

    • 您好,谢谢您的回答。 getTrackData 只是一个吸气剂,它并不昂贵。我需要这个缓存,因为多个曲目可以指向同一个 TrackData,即一个曲目只是播放列表中的一个条目,所以我可以有多个曲目副本指向同一个 TrackData。所以我需要缓存才能共享对象,而不是加快访问时间。
    • @tulskiy,嗯。您是使用构造函数/设置器将TrackData 添加到Track 中还是使用其他字段计算?
    • @Stas Kurilin:它是在我从文件中读取标签和其他信息时创建的。
    • 谢谢,我看看能不能覆盖 Tracks 的 equals 方法。有时我需要依赖具有相同数据的不同轨道实例才能有所不同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-11
    • 2022-11-27
    • 1970-01-01
    • 1970-01-01
    • 2013-12-23
    • 2011-02-21
    相关资源
    最近更新 更多