Сложный ключ для гуавы Тайник (перекладной)

У меня есть точечный объект:

  class Point {
    final int x,y;
    ...
  }

Поскольку эти точки будут использоваться/создаваться повсюду в моем коде, я хочу начать использовать кеш гуавы. К сожалению, CacheLoader принимает только один параметр. Еще один вопрос здесь, в stackoverflow, используйте объект пары для аналогичная проблема. Но мне не нравится идея создавать фиктивный объект для каждого запроса кэша. Поэтому я придумываю свой собственный обходной путь:

Поскольку объект определяется x и y, я думаю, что могу объединить (сдвинуть) оба значения в длинное, которое будет моим ключом.

void test(int x, int y) {
    Long key = (long) ((long) (x) << Integer.SIZE | y);
    Point point = cache.get(key);
}

CacheLoader<Long, Point> loader = new CacheLoader<Long, Point>() {
    public Point load(Long key) throws Exception {
    final int x,y;
        // shift magic
        x = (int) (key >> Integer.SIZE);
        y = key.intValue();
        return new Point(x, y);
    }
};

На самом деле я новичок в смене. Будет ли это работать? Я что-то пропустил? Это «быстрее», чем парный класс? Это мой вопрос!

Да, я тестирую код, и насколько я могу судить, он работает.


person Marcel Jaeschke    schedule 23.11.2011    source источник
comment
эй. Пожалуйста создайте реальный объект (с двумя правильно названными полями) для точки! Вы уже создаете «фиктивный» объект, java.lang.Long, только более запутанный.   -  person Dimitris Andreou    schedule 23.11.2011


Ответы (2)


Как насчет этого? Ваш класс Point должен правильно реализовать equals() и hashcode().

static class Points {
  static final Interner<Point> INTERNER = Interners.newStrongInterner();

  public static Point cached(final int x, final int y) {
    return INTERNER.intern(new Point(x, y));
  }
}

Ваша настоящая цель состояла в том, чтобы кэшировать одинаковые объекты, верно? Тогда этого будет достаточно для ваших нужд. Применение:

Point center = Points.cached(0, 0);

Или скорректированная версия вашего примера кеша:

CacheLoader<Point, Point> loader = new CacheLoader<Point, Point>() {
  @Override
  public Point load(final Point point) {
    return point;
  }
}
...
Point center = cache.get(new Point(0, 0));
person bjmi    schedule 23.11.2011
comment
Этот Интернер для меня новый. Выглядит интересно. Спасибо! Конечно, правильно реализованы equals и hashcode. Я не хочу, чтобы мистер Блох отправил меня в Java-ад :-D - person Marcel Jaeschke; 23.11.2011
comment
Ой. Очень плохая идея. Во-первых, вы превращаете объекты с крошечным жизненным циклом (кусок пирога для всех сборщиков мусора) в постоянные объекты, замедляя все будущие полные сборщики мусора. Затем эта штука разрастается без ограничений, мало чем отличаясь от утечки памяти. В-третьих, просто для безубыточности с точки зрения памяти все кешированные точки должны сохраняться как минимум в двух местах. Только если они будут повторно использоваться больше, будет экономия памяти. Не говоря уже о том, что эти объекты не нужно держать под рукой, кроме как для поиска, не говоря уже о том, чтобы хранить их в двух местах. Я бы подумал об удалении этого ответа :-\ - person Dimitris Andreou; 24.11.2011
comment
Ты прав. Реализация StrongInterner будет хранить баллы до конца. Interners.newWeakInterner() сделает свое дело. Выгода достигается только в том случае, если баллы действительно используются повторно. - person bjmi; 24.11.2011
comment
На самом деле WeakInterner не будет иметь никакого эффекта, поскольку он сравнивает ключи, используя == вместо .equals(). (Это не так хорошо задокументировано, как должно быть, но источник довольно ясен.) Лучшее решение почти наверняка — просто не проводить стажировку вообще. GC в Java отлично подходит для сбора недолговечных маленьких объектов; накладные расходы в принципе незначительны. Идите вперед и создавайте новую точку каждый раз. - person Louis Wasserman; 28.11.2011
comment
@Louis Wasserman: я не согласен с w.r.t. WeakInterner. Если бы он использовал ==, то он вообще ничего не мог бы интернировать. Более того, в коде я вижу .keyEquivalence(Equivalence.equals()). - person maaartinus; 25.09.2012

Это, вероятно, быстрее (если разница вообще измерима), но парный класс сделает ваш код намного лучше для понимания или повторного использования. Я бы пошел с общим классом Pair:

public final class Pair<L, R> {

    private final L left;
    private final R right;

    private Pair(L left, R right) {
        this.left = left;
        this.right = right;
    }

    public static <L,R>Pair<L,R> of(L left, R right){
        return new Pair<L, R>(left,right);
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof Pair) {
            final Pair<?,?> other = (Pair<?,?>) obj;
            return Objects.equal(left,  other.left)
                && Objects.equal(right, other.right);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(left, right);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                      .add("left", left)
                      .add("right", right)
                      .toString();
    }

}

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

Документация по Guava

person Sean Patrick Floyd    schedule 23.11.2011
comment
Ладно, может быть, разница не измерима. Понятно! Но каковы недостатки, которые вы упомянули?! Это был один из моих вопросов в первую очередь. - person Marcel Jaeschke; 23.11.2011
comment
@MarcelJaeschke с использованием объектов делает ваш код читаемым, понятным и пригодным для повторного использования. битовый сдвиг не работает. Если вы спросите меня, это преждевременная оптимизация. - person Sean Patrick Floyd; 23.11.2011
comment
Да, я очень хорошо знаю концепцию ООП! Но я думаю, что ваш способ - это просто своего рода чрезмерная инженерия! Точка объекта состоит только из двух полей int. В вашем парном классе этих полей два! Фактически вы создаете копию объекта! Таким образом, вы можете использовать объект в качестве ключа! Я имею в виду (извините за формат): Point point = new Point(x,y); Точка pointFromCache = cache.get(point); CacheLoader‹Point, Point› loader = new CacheLoader‹Point, Point›() { public Point load(Point key) throws Exception { return key; } }; - person Marcel Jaeschke; 23.11.2011
comment
@MarcelJaeschke а) не бойтесь создания объектов. Текущая виртуальная машина Java на текущей машине может обрабатывать и обрабатывает тысячи созданий объектов в секунду. Кстати, вы также создаете объект со сдвигом битов, потому что используете объект Long. б) класс пары, как я уже писал, имеет смысл, если его использовать повторно. Правда, он создан для одноразового использования, но как только он у вас появится, вы найдете для него множество других применений. - person Sean Patrick Floyd; 23.11.2011
comment
'Point point = new Point(x,y); Точка pointFromCache = cache.get(point); CacheLoader‹Point, Point› loader = new CacheLoader‹Point, Point›() { public Point load(Point key) throws Exception { return key; } }' - person Marcel Jaeschke; 23.11.2011
comment
И говоря о перепроектировании: класс точек мне кажется чем-то, что не нужно кэшировать в первую очередь, его кэширование, вероятно, делает приложение (минимально) медленнее, чем воссоздание объектов, если у вас нет каких-то сложных процедур инициализации что вы нам не показываете. - person Sean Patrick Floyd; 23.11.2011
comment
Что-то вроде. Некоторые части кода всегда будут использовать/создавать точки независимо друг от друга. Например. ›10000000 экземпляров точек (50к уникальных). Поэтому я думаю, что имеет смысл кэшировать эти точки. - person Marcel Jaeschke; 23.11.2011