我有两个数字,想要将它们一起作为Map
的键使用。目前,我正在连接它们的字符串表示。例如,假设关键数字是4和12。 我使用以下代码:
String key = 4 + "," + 12;
这个 map 被声明为 Map<String, Object>
。
我认为这样做很糟糕!我想使用除了 String
以外的其他内容作为键!我要找到创建这些键最快的方法。
有谁有好主意吗?
我有两个数字,想要将它们一起作为Map
的键使用。目前,我正在连接它们的字符串表示。例如,假设关键数字是4和12。 我使用以下代码:
String key = 4 + "," + 12;
这个 map 被声明为 Map<String, Object>
。
我认为这样做很糟糕!我想使用除了 String
以外的其他内容作为键!我要找到创建这些键最快的方法。
有谁有好主意吗?
class Coordinates {
private int x;
private int y;
public Coordinates(int x, int y) {
...
}
// getters
// equals and hashcode using x and y
}
Map<Coordinates, Location> locations = new HashMap<Coordinates, Location>();
如果你更喜欢用数学方法,可以查看这个StackOverflow答案。
你应该使用java.awt.Dimension作为你的键。
Dimension key = new Dimension(4, 12);
Dimension有一个非常好的hashCode()方法,为每对正整数生成不同的hashCode,因此(4, 12)和(12, 4)的hashCodes是不同的。因此这些对象实例化快且可用于很好的哈希码。
我希望他们将该类设置为不可变的,但是你可以自己创建一个模仿Dimension的不可变类。
以下是一个表格,显示不同宽度和高度的hashCode:
0 1 2 3 4 <-- width
+--------------------
0 | 0 2 5 9 14
1 | 1 4 8 13
2 | 3 7 12
3 | 6 11
4 | 10
^
|
height
如果您按照从0到14的顺序跟随哈希码,就会看到这个模式。
以下是生成此哈希码的代码:
public int hashCode() {
int sum = width + height;
return sum * (sum + 1)/2 + width;
}
你可能会在最后一行中认出三角数的公式。这就是为什么表格的第一列包含所有的三角数。
为了提高速度,你应该在构造函数中计算hashCode。因此,你的整个类可能看起来像这样:
public class PairHash {
private final int hash;
public PairHash(int a, int b) {
int sum = a+b;
hash = sum * (sum+1)/2 + a;
}
public int hashCode() { return hash; }
}
当然,如果您可能需要一个equals方法,但限制自己在不会溢出的正整数范围内,您可以添加一个非常快的equals方法:
public class PairHash {
// PAIR_LIMIT is 23170
// Keeping the inputs below this level prevents overflow, and guarantees
// the hash will be unique for each pair of positive integers. This
// lets you use the hashCode in the equals method.
public static final int PAIR_LIMIT = (int) (Math.sqrt(Integer.MAX_VALUE))/2;
private final int hash;
public PairHash(int a, int b) {
assert a >= 0;
assert b >= 0;
assert a < PAIR_LIMIT;
assert b < PAIR_LIMIT;
int sum = a + b;
hash = sum * (sum + 1) / 2 + a;
}
public int hashCode() { return hash; }
public boolean equals(Object other) {
if (other instanceof PairHash){
return hash == ((PairHash) other).hash;
}
return false;
}
}
我们限制这个方法只适用于正数,因为负数会产生一些重复的哈希码。但是在这个限制下,这些代码是可以编写最快的hashCode()和equals()方法的。(当然,在任何不可变类中都可以通过在构造函数中计算hashCode来编写同样快的hashCode。)
如果您无法接受这些限制,您只需要保存参数即可。
public class PairHash {
private final int a, b, hash;
public PairHash(int a, int b) {
this.a = a;
this.b = b;
int sum = a+b;
hash = sum * (sum+1)/2 + a;
}
public int hashCode() { return hash; }
public boolean equals(Object other) {
if (other instanceof PairHash) {
PairHash otherPair = (PairHash)other;
return a == otherPair.a && b == otherPair.b;
}
return false;
}
但这是重点。你根本不需要这个类。由于该公式为每对数字提供唯一的整数,因此您可以将该整数用作映射键。Integer类具有自己的快速equals()和hashCode()方法,这些方法将正常工作。此方法将从两个short值生成哈希键。限制是您的输入需要是正的short值。这保证不会溢出,并且通过将中间总和转换为长整型,它的范围比之前的方法更广:它适用于所有正的short值。
static int hashKeyFromPair(short a, short b) {
assert a >= 0;
assert b >= 0;
long sum = (long) a + (long) b;
return (int) (sum * (sum + 1) / 2) + a;
}
hashCode()
方法返回的哈希码相匹配。此时你基本上就遇到了麻烦。例如,使用java.awt.Point
-- 看起来就像是您想要的东西--以下内容:
public static void main(String[] args) {
Map<Point, Object> map = new HashMap<Point, Object>();
Point key = new Point(1, 3);
Object val = new Object();
map.put(key, val);
System.out.println(map.containsKey(key));
System.out.println(map.containsKey(new Point(1, 3)));
// equivalent to setLeft() / setRight() in ZZCoder's solution,
// or setX() / setY() in SingleShot's
key.setLocation(2, 4);
System.out.println(map.containsKey(key));
System.out.println(map.containsKey(new Point(2, 4)));
System.out.println(map.containsKey(new Point(1, 3)));
}
打印:
true
true
false
false
false
您可以像这样在 long 类型中存储两个整数:
long n = (l << 32) | (r & 0XFFFFFFFFL);
Pair<Integer, Integer>
类,public class Pair<L, R> {
private L l;
private R r;
public Pair() {
}
public Pair(L l, R r) {
this.l = l;
this.r = r;
}
public L getLeft() {
return l;
}
public R getRight() {
return r;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Pair)) {
return false;
}
Pair obj = (Pair) o;
return l.equals(obj.l) && r.equals(obj.r);
}
@Override
public int hashCode() {
return l.hashCode() ^ r.hashCode();
}
}
hashCode = a + b * 17;
...其中a、b和hashCode都是整数。17只是一个任意的质数。你的哈希值不会是唯一的,但没关系。这种东西在Java标准库中随处可见。
另一种方法是使用嵌套的映射:
Map<Integer,Map<Integer,Object>>
在这里,您不需要创建键的额外开销。但是,您需要更多的开销来正确地创建和检索条目,并且您始终需要映射访问才能找到所需的对象。
为什么编写所有额外代码以创建一个不需要的完整类,比使用简单的字符串好?计算该类的实例哈希码是否比字符串快得多?我认为并不是。
除非您在极度有限的计算能力环境中运行,否则制作和散列字符串的开销不应该明显大于实例化自定义类的开销。
我想最快的方法是按照 ZZ Coder 的建议将 int 打包成单个 Long,但无论如何,我不指望速度的增益会很大。
你需要编写正确的equals和hashcode方法,否则会产生一些错误。