【问题标题】:A complex key for guavas Cache (shifting)番石榴缓存的复杂键(移位)
【发布时间】:2012-01-04 15:57:36
【问题描述】:

我有一个点对象:

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

因为这些点将在我的代码中到处使用/创建,所以我想开始使用番石榴缓存。不幸的是,CacheLoader 只接受一个参数。 Another question 在 stackoverflow 上使用 pair 对象来解决类似的问题。但我不喜欢为每个缓存请求创建一个虚拟对象的想法。 所以我想出了自己的解决方法:

因为对象是由 x 和 y 指定的,我想我可以将两个值合并(移位)成一个 long,这将是我的关键。

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);
    }
};

我其实是个新手。这行得通吗?我错过了什么?这比对类“更快”吗?这是我的问题!

是的,我测试了代码,到目前为止我可以判断它是否有效。

【问题讨论】:

  • 哎呀。 为该点创建一个真实对象(带有两个命名良好的字段)!您已经在创建一个“虚拟”对象,一个 java.lang.Long,只是更加模糊。

标签: java caching guava shift


【解决方案1】:

这个怎么样?您的 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));

【讨论】:

  • 哼这个实习生对我来说是新的。看起来很有趣。谢谢!当然是equalshashcode 正确实施。我不希望 Bloch 先生把我带入 Java 地狱 :-D
  • 哎哟。非常糟糕的主意。首先,您将生命周期很小的对象(所有垃圾收集器的小菜一碟)转变为终身对象,从而使所有未来的完整 gc 变慢。然后,这件事变得无限,与内存泄漏没有太大区别。第三,为了在内存方面收支平衡,所有缓存点必须至少保留在两个地方。只有更多地重复使用它们才能节省内存。更不用说这些对象不需要保留,除了查找之外,更不用说保留在两个地方了。我会考虑删除这个答案:-\
  • 你是对的。 StrongInterner 实现将存储积分直到结束。 Interners.newWeakInterner() 可以解决问题。只有真正重复使用积分才能获得好处。
  • WeakInterner 实际上不会产生任何影响,因为它使用 == 而不是 .equals() 比较键。 (这并没有得到应有的充分记录,但来源很清楚。)几乎可以肯定,最好的解决方案是根本不做任何实习。 Java 的 GC 擅长收集短命的小对象;开销基本上可以忽略不计。来吧,每次都创建一个新点。
  • @Louis Wasserman:我不同意 w.r.t. WeakInterner。如果它使用==,那么它根本不能实习任何东西。此外,在代码中我可以看到.keyEquivalence(Equivalence.equals())
【解决方案2】:

它可能更快(如果差异是可测量的),但 pair 类将使您的代码更易于理解或重用。我会选择一个通用的 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 docs

【讨论】:

  • 好吧,差异可能无法衡量。知道了!但是你提到的缺点是什么?!这是我的一个问题。
  • @MarcelJaeschke 使用对象使您的代码具有可读性、可理解性和可重用性。位移没有。如果你问我,这是过早的优化。
  • 是的,我非常了解 OOP 的概念!但我认为你的方式只是一种过度工程!对象点仅由两个 int 字段组成。你的配对班有这两个领域!实际上,您创建了对象的副本!因此,您实际上可以首先将对象用作键!我的意思是(对不起格式): Point point = new Point(x,y);点pointFromCache = cache.get(point); CacheLoader loader = new CacheLoader() { public Point load(Point key) throws Exception { return key; } };
  • @MarcelJaeschke a) 不要害怕创建对象。当前机器上的当前 Java VM 可以并且确实每秒处理数千个对象创建。顺便说一句,您还使用位移位创建对象,因为您使用的是 Long 对象。 b) 正如我所写的那样,pair 类如果你重用它就有意义。的确,它被过度设计为一次性使用,但一旦你拥有它,你会发现它的更多用途。
  • 'Point point = new Point(x,y);点pointFromCache = cache.get(point); CacheLoader loader = new CacheLoader() { public Point load(Point key) throws Exception { return key; } }'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多