Реализация кеша с использованием мягких ссылок как для ключей, так и для значений

Я хотел бы реализовать кеш, ключи и значения которого являются мягкими ссылками, потому что экземпляры, которые он будет содержать, могут занять много времени для вычисления (особенно значений), и я хотел бы, чтобы записи, содержащие объекты, на которые не ссылались, как ключи или как значения чтобы собрать мусор тогда и только тогда, когда мне не хватит памяти. Если бы я использовал слабые ссылки для значений, они были бы собраны мусором, как только на них не будут ссылаться, а это не то, что я хочу делать.

Сначала я использовал этот пример. Он работает, но создание одного потока для каждого экземпляра кеша меня раздражает, он использует сильные ссылки для ключей и удаление устаревших записей самостоятельно (например, WeakHashMap) в некоторых вызовах методов класса кеша не работает (очевидно), когда я рискую запустить не хватает памяти, когда я им не звоню. Я хотел использовать Guava, но MapMaker больше не позволяет использовать программные клавиши, что логично, поскольку эквивалентность по умолчанию основана на равенстве (==), а не на методе equals (), что означает, что невозможно воссоздать идентичный ключ. Однако я согласен с комментарием Себастьяна-Лорбера:

Я думаю, что программные клавиши имели бы смысл, если бы Guava переопределял метод equals в SoftReference. Я видел, что Guava использует «механизм эквивалентности», и я думаю, что для мягких ссылок значение defaultEquivalence не должно быть тождественным, а равным, чтобы справиться с таким случаем.

Я тоже посмотрел на MapDB и JCS.

Как я могу изменить приведенный выше пример или использовать Guava для создания кеша на основе мягких ссылок, предпочтительно используя equals () вместо == для определения равенства ключей?


person gouessej    schedule 04.09.2014    source источник


Ответы (2)


Я думаю, что единственное идеальное решение - это иметь CacheBuilder.softKeysDelegatingEquals в Guava, как вы предложили.

Переходите к концу, чтобы получить более подробное представление.

Возможно, этот обходной путь подойдет:

  • всякий раз, когда вы помещаете что-нибудь в Cache, вставляйте новый SoftReference<Key> в свой собственный HashSet.
  • добавьте removalListener к Cache и удалите SoftReference из HashSet

В любом случае, вам не нужна нить.

Вторая часть немного сложна, так как вам дали Key и вам нужно удалить соответствующую мягкую ссылку. Для этого вы можете либо заменить набор на WeakHashMap<Key, SoftReference<Key>, либо взломать, позволив вашему ключу equals быть ссылкой:

class Key {
    Key(Somedata somedata) {
        this.somedata = somedata;
    }

    @Override public int hashCode() {
        return somedata.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj instanceof Key) {
            return somedata.equals(((Key) obj).somedata);
        }
        if (obj instanceof KeySoftReference) {
            return equals(((KeySoftReference) obj).get());
        }
        return false;
    }

    @NonNull private final Somedata somedata;
}

// Hacky, never use for anything else.
class KeySoftReference extends SoftReference<Key> {
    protected KeySoftReference(Key key) {
        super(key);
        this.hashCode = key.hashCode();
    }

    @Override public int hashCode() {
        return hashCode;
    }

    @Override public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj instanceof Key) {
            return ((Key) obj).equals(this);
        }
        if (obj instanceof KeySoftReference) {
            Key key = get();
            // This makes no two cleared reference equal to each other,
            // That's OK as long as you never create two references to the same object.
            return key!=null && key.equals(((KeySoftReference) obj).get());
        }
        return false;
    }

    private final int hashCode;
}

Я думаю, это удовлетворяет условиям контракта, но, как я уже сказал, это хакерский и непроверенный.

ОБНОВИТЬ

Есть простое решение. Использовать

CacheBuilder.weakKeys().softValues()

и добавьте SoftReference<Key> к вашему Value. Это делает ключ легко доступным.

Если вам действительно нужно сравнение на равенство, то вам, вероятно, не повезло, хотя WeakInterner<Key> может помочь.

person maaartinus    schedule 04.09.2014
comment
Ваше последнее решение кажется очень интересным. Я попробую. Большое спасибо за очень полный ответ. Я дам вам обратную связь. - person gouessej; 05.09.2014

На самом деле, я только что повторно ввел программные клавиши в Guava (удаленные из него в версии 14), но в CacheBuilder: https://code.google.com/p/guava-libraries/issues/detail?id=1845

Затем мне нужно вызвать закрытый метод CacheBuilder.keyEquivalence (Equivalence.equals ()), чтобы использовать equals () вместо == для сравнения ключей. Я дам вам знать, работает ли это. В этом случае я приму свой ответ. В противном случае мне придется использовать предложения маартинуса.

person gouessej    schedule 05.09.2014