Java: нужно посоветовать насчет WeakHashMap - PullRequest
3 голосов
/ 04 декабря 2010

Полагаю, я другой человек, пытающийся создать какой-то кеш с помощью WeakHashMap. И мне нужна помощь с этим.

У меня есть куча 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 () для проверки местоположения и индекса subongong) ). Я хочу использовать слабые ссылки, поэтому мне не нужно заботиться об удалении треков.

Я хотел спросить, нормально ли хранить слабую ссылку на ключ в WeakHashMap, и если нет, как мне подойти к этой проблеме? Мне нужны слабые ссылки и постоянное время получения кэшированных значений. Я думал о копировании кода WeakHashMap и обнародовании метода getEntry(), который решает проблему, но это такой плохой взлом: (

PS. Я понимаю, что коллекции apache или google могут иметь что-то подобное, но я действительно не хочу добавлять 2-мегабайтные зависимости.

Ответы [ 2 ]

2 голосов
/ 05 декабря 2010

Я бы рекомендовал заменить WeakReferences на SoftReferences.

Любые объекты, на которые ссылается только WeakReference, являются целью для каждого раунда сборщика мусора. Это означает, что ваш кеш можно очистить, даже если свободной памяти все еще достаточно.

Если вы замените WeakReference на SoftReference, тогда вы заявите: Удалять объект, на который имеется ссылка, только в том случае, когда абсолютно нет свободной памяти для выделения.

В java нет готовой к использованию реализации SoftHashMap. В гуаве есть хороший пример - MapMaker. Стоит использовать этот хорошо проверенный и проверенный код в производственных средах, а не предоставлять свою реализацию с явно меньшим качеством. Он также обладает удивительным механизмом самоочищения:

  1. вы можете указать максимальный размер кэша :

    По мере того, как размер карты приближается к максимум, карта будет выселять записи которые с меньшей вероятностью будут использоваться снова. Например, карта может выселить запись, потому что она не была использована недавно или очень часто.

  2. Вы можете указать срок действия для записей карты с помощью методов expireAfterWrite и expireAfterAccess.

Мне также кажется, что ваш дизайн кеша не очень удобен. Как я понял из вашего фрагмента кода, с самого начала ваши Track имеют сильные ссылки на их TrackData, и вы строите свой кеш на этих обстоятельствах. Но с какого-то момента вы хотите использовать кеш для извлечения данных, поэтому вам придется создавать новые Track с другим способом, потому что с того момента вы хотите использовать кеш но не сильные ссылки.

Разные Tracks могут иметь одинаковые TrackData, поэтому мы не можем использовать Track в качестве ключа. Итак, я бы пошел со следующим подходом:

  1. ввести промежуточный уровень идентификаторов и создать кеш на основе Map<Integer, TrackData> с мягкими значениями и определенной стратегией самоочищения (на основе MapMaker);
  2. изменить отношение Track --> TrackData на Track --> Id (int). Кеш Id --> TrackData.
1 голос
/ 04 декабря 2010

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;

   }

 }

Обратите внимание, что WeakHashMap ничего не будет делать ни в одном из двух решений, пока существуют живые объекты Track, сохраняющие ссылки на TrackData. Это можно исправить, сделав WeakReference внутри Track - однако это также означает, что у вас может не быть TrackData, и вам нужно будет прочитать его обратно из файла, и в этом случае первое решение лучше смоделировано, чем второе .

...