4

在我的程序中,我想使用带有两个键(整数)的 Map。我的第一个想法是以某种方式将整数连接成一个字符串,例如:

String key = k1.toString()+"-"+k2.toString();

这个解决方案对我来说看起来不太好:1)丑陋;2)慢(将数字作为文本处理)。

我在 stackoverflow 上发现了其他方法。它们基于将整数封装在一个类中——一个目的类(MyKey),或更通用的一个(Pair)。

我尝试进行一些速度测试,我的虚拟解决方案似乎是最快的。第一次拍摄后,我尝试将转换整数字符串封装在一个新类(MyString)中,并针对该解决方案运行测试。

地图定义是:

Map<Pair<Integer,Integer>,String> map1 = new HashMap<>();
Map<MyKey,String> map2 = new HashMap<>();
Map<String,String> map3 = new HashMap<>();
Map<MyString,String> map4 = new HashMap<>();

测试结果是(运行多次,似乎稳定):

  map: put+get=total
  1: 52+154=206
  2: 29+77=106
  3: 23+49=72
  3: 17+55=72

带字符串的解决方案更快。字符串键的直接连接在搜索时更快,在输入时更慢。

我的问题是:

1) 为什么使用 String 的解决方案更快?(一次调用 hashCode()?)

2) 有什么理由不应该使用 String 的解决方案吗?


附加信息:

地图中的记录数约为 6000 条。

测试还试图获取许多不存在的键的值。它会改变测试结果吗?

在我的程序中,我生成 boolean[N] 的排列,其中 M 值为真。一次,我得到某个 N,M 的结果;我想保存它们以备不时之需。

这是我的示例中使用的类的完整代码:

  class Pair<L,R> {

    private final L left;
    private final R right;

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

    public L getLeft() { return left; }
    public R getRight() { return right; }

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

    @Override
    public boolean equals(Object o) {
      if (o == null) return false;
      if (!(o instanceof Pair)) return false;
      Pair pairo = (Pair) o;
      return this.left.equals(pairo.getLeft()) &&
             this.right.equals(pairo.getRight());
    }
  }

  class MyKey {
      public Integer k1;
      public Integer k2;

      public MyKey(Integer k1, Integer k2) {
          this.k1 = k1;
          this.k2 = k2;
      }

      @Override
      public int hashCode() {
          return k1.hashCode() + 17 * k2.hashCode();
      }

      @Override
      public boolean equals(Object o) {
          if (o == this) {
              return true;
          }
          if (o == null || !(o instanceof MyKey)) {
              return false;
          }
          MyKey cp = MyKey.class.cast(o);
          return k1.equals(cp.k1) && k2.equals(cp.k2);
      }
  }

  class MyString  {
      private String value;

      public MyString(Integer k1, Integer k2) {
          value=k1+"-"+k2;
      }

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

      @Override
      public boolean equals(Object o) {
          return o.equals(value);
      }
  }
4

3 回答 3

5

这应该是性能最高的双整数键:

class MyKey {
  public final int k1, k2;
  MyKey(int k1, int k2) { this.k1 = k1; this.k2 = k2; }
  public int hashCode() { return k1 ^ k2; }
  public boolean equals(Object o) { 
    MyKey that;
    return o instanceof MyKey && (that = (MyKey)o).k1 == k1 && that.k2 == k2;
  }

至于您的测试结果,您应该非常小心地进行微基准测试。你确定你做了所有的咒语,比如热身、GC-ing、仔细编写 JIT 无法编译出来的代码等吗?如果没有,我强烈推荐 Google Caliper,而不是重新发明轮子。

于 2012-11-25T20:16:29.263 回答
1

您遇到的最大问题是构建字符串,或者创建对象只是为了执行查找。

解决此问题的一种方法是使用 Map 或 Map 值。由于您的密钥是原语,因此您最好使用 trove 库。TObjectIntHashMapTIntIntHashMap

例如

TObjectIntHashMap<TIntIntHashMap> map = ...
int val = map.get(k1).get(k2);

使用这种方法,不需要任何对象来创建键或值。

如果要配对密钥,可以使用以下命令

TLongIntHashMap map = ...
int val = map.get(((long) k1 << 32) | k2);

例如

long key = ((long) k1 << 32) | k2;
map.adjustOrPut(key, 1, 1); // a counter for this key.
于 2012-11-25T21:00:34.647 回答
0

2) 有什么理由不应该使用 String 的解决方案吗?

如果您询问给定的方法:

String key = k1.toString()+"-"+k2.toString();

问题是:

k1 = "a-b"
k2 = "c"

k1 = "a"
k2 = "b-c"

(和类似的)

有相同的键。

如果您询问使用课程:

有一个处理这个的类更干净。因为那时你的班级关心的是实现,而不是调用者。这意味着,您不必考虑是使用“-”还是“。” 或“#”或任何现在正确的东西,如果你想改变它,你可以在课堂上改变它。不在源代码周围散布的不同位置。

哈希码实现的正确方法取决于您的数据。Eclipse 建议一个通用的方法:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((k1 == null) ? 0 : k1.hashCode());
    result = prime * result + ((k2 == null) ? 0 : k2.hashCode());
    return result;
}

这对我来说看起来不错。

问题 1 稍微复杂一些。它在很大程度上取决于输入数据。

一般建议:不要关心性能,只要你不必关心它。这意味着,只有当解决方案太慢时,才开始对其进行分析并改进最重要的部分。除此之外,可读性和可维护性始终是首要目标。

于 2012-11-25T20:18:44.410 回答