带有两个密钥的Java Map - 解决方案的速度比较

时间:2012-11-25 19:42:50

标签: java performance map

在我的程序中,我想使用带有两个键的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。

测试试图获取许多未显示键的值。它可以改变测试结果吗?

在我的程序中,我生成布尔值[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);
      }
  }

3 个答案:

答案 0 :(得分: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,而不是重新发明轮子。

答案 1 :(得分:1)

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

解决这个问题的方法是获得Map或Map值。由于你的键是原始的,你最好使用特洛伊库。 TObjectIntHashMapTIntIntHashMap

e.g。

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

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

如果您想配对密钥,可以使用以下

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

e.g。

long key = ((long) k1 << 32) | k2;
map.adjustOrPut(key, 1, 1); // a counter for this key.

答案 2 :(得分: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有点复杂。它在很大程度上取决于输入数据。

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