Кэширование объектов, построенных с несколькими параметрами - PullRequest
0 голосов
/ 14 ноября 2010

У меня есть фабрика, которая создает объекты класса MyClass , возвращая уже созданные объекты, когда они существуют. Как у меня есть метод создания ( getOrCreateMyClass ), принимающий несколько параметров, который является лучшим способом использовать карту для хранения и извлечения объектов?

Мое текущее решение заключается в следующем, но оно не кажется мне слишком ясным. Я использую метод hashCode (слегка измененный) класса MyClass для создания int на основе параметров класса MyClass, и я использую его в качестве ключа 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;
    }

}

Ответы [ 2 ]

3 голосов
/ 15 ноября 2010

Ваш getOrCreateMyClass, похоже, не добавляет в кеш, если создает.

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

Вы можете создать общий класс Pair с реальными методами equals и hashCode и использовать класс Pair<String, Integer> в качестве ключа карты для своего кэша.

Изменить:

Вопрос о дополнительном потреблении памяти путем хранения ключа Pair<String, Integer> и значения MyClass может быть решен наилучшим образом, если сделать Pair<String, Integer> в поле MyClass и, таким образом, иметь только одну ссылку на этот объект .

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

И действительно ли это вообще хорошая идея, зависит от того, намного ли дороже создание MyClass, чем создание ключа карты.

Другое Редактирование:

Ответ ColinD также является разумным (и я проголосовал за него), пока конструкция MyClass не дорогая.

Другим подходом, который может стоить рассмотреть, является использование вложенной карты Map<String, Map<Integer, MyClass>>, которая потребует двухэтапного поиска и немного усложнит обновление кэша.

2 голосов
/ 15 ноября 2010

Вы действительно не должны использовать хеш-код в качестве ключа на вашей карте.Хеш-код класса не предназначен для того, чтобы обязательно гарантировать, что он не будет одинаковым для любых двух неравных экземпляров этого класса.Действительно, ваш метод хеш-кода может определенно создать один и тот же хеш-код для двух неравных экземпляров.Вам нужно для реализации equals на MyClass, чтобы проверить, что два экземпляра MyClass равны на основе равенства String и int, которые они содержат.Я также рекомендовал бы сделать поля s и i final, чтобы обеспечить более надежную гарантию неизменности каждого экземпляра MyClass, если вы собираетесь использовать его таким образом.

Кроме того, я думаю, что вам на самом деле здесь нужен интернер .... то есть что-то, что гарантирует, что вы когда-либо сохраните не более 1 экземпляра данного MyClass в памяти навремя.Правильным решением этого является Map<MyClass, MyClass> ... точнее ConcurrentMap<MyClass, MyClass>, если есть вероятность вызова getOrCreateMyClass из нескольких потоков.Теперь вам нужно создать новый экземпляр MyClass, чтобы проверить кэш при использовании этого подхода, но на самом деле это неизбежно ... и это не имеет большого значения, потому что MyClass легко создать.

Guava имеет кое-что, что делает всю работу за вас здесь: интерфейс Interner и соответствующий Interners фабричный / служебный класс.Вот как вы можете использовать его для реализации 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, этот экземпляр будет иметь право на сборку мусора, что поможет вам не тратить память на экземпляры, которые вам не нужны.

Если вы решите сделать это самостоятельно, вы захотите использовать ConcurrentMap кеш и использовать putIfAbsent.Вы можете взглянуть на реализацию сильного интерна в Guava для справки, я думаю ... подход слабых ссылок намного сложнее.

...