在我的程序中,我想使用带有两个键的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);
}
}
答案 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值。由于你的键是原始的,你最好使用特洛伊库。 TObjectIntHashMap和TIntIntHashMap
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有点复杂。它在很大程度上取决于输入数据。
一般建议:不要关心表现,只要你不必照顾它。这意味着,只有当解决方案太慢时,才开始对其进行分析并改进最重要的部分。除此之外,可读性和可维护性始终是第一目标。