如何从 3 个整数组成 HashMap 的键?

How to compose a key for a HashMap from 3 integers?

我需要一个从 3 个整数到一个对象的唯一映射。我不能对这些整数的值做任何假设,除了它们是正数。

我目前使用这样的哈希图来实现它:

int a = 10;
int b = 20;
int c = 30;
Map<String, MyObject> map = new HashMap<>();
map.put(a + "_" + b + "_" + c, myObject);

虽然这行得通,但看起来有点丑。

有没有更好的选择,既不引入冲突,又不使用第 3 方库?

编辑:我刚刚意识到我实际上可以对我的整数做出更具体的假设。它们都在 1,000,000 到 2,000,000 之间,权重在底部,因为它们是从 1,000,000 开始计数的序列。

final class Key{
     int a , b , c;
     public Key(int a , int b , int c){
         this.a = a;
         this.b = b;
         this.c = c;
     }

     public int hashCode(){
          return (a << 10 ^ b << 5 ^ c);
     }

     public boolean equals(Object o){
          if(!(o instanceof Key))
               return false;

          Key k = (Key) o;

          return (k.a == a && k.b == b && k.c == c);
     }
}

即使是负值也应该有效。

您可以像这样创建 Key class:

final class Key {
    final int a;
    final int b;
    final int c;

    Key(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Key)) {
            return false;
        }
        Key that = (Key) obj;
        return (this.a == that.a)
               && (this.b == that.b)
               && (this.c == that.c);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.a, this.b, this.c);
    }

}

并使您的地图成为 Map<Key, MyObject>

与使用 String 相比,它的一个优点是类型安全。您将无法执行此操作,例如:

map.put("Some arbitrary string that has nothing to do with numbers", myObject);

正如某些答案所指出的那样,拥有自己的密钥 class 就可以了。像往常一样使用 hashcode 和 equals 检测冲突。

就个人而言,我喜欢选择无限的整数数组以提高灵活性。

public final class IntArrayKey{
    private final List<Integer> values = new ArrayList<>();
    private final int hash;
    private final String stringValue;

    public IntArrayKey(Integer ...ints){
        values.addAll(Arrays.asList(ints));
        hash = Arrays.hashCode(ints);
        stringValue = values.toString();
    }

    @Override
    public boolean equals(Object o){
        if(o == this) return true;
        if(!(o instanceof IntArrayKey)) return false;
        return values.equals(((IntArrayKey) o).values);
    }

    @Override
    public int hashCode(){
        return hash;
    }

    @Override
    public String toString(){
        return stringValue;
    }
}

因为每个值的范围最多有2M个不同的连续值,即在221个值以下,并且有3个这样的值,不同组合的数量是低于 263。如此多的组合正好(刚好)适合 long 值的范围 (264),因此通过正确的数学运算,a、b 和 c 的每个唯一组合都可以有其拥有 long 价值。

这是一个简洁且高性能的工具,它使用位操作来完成工作:

// safe for a, b and c in range 0-2,097,151 (21 bits)
private static long key(long a, long b, long c) {
    return a << 42 | b << 21 | c;
}

如果您将地图的键类型设置为Long,那么您可以使用此方法的结果作为键:

Map<Long, MyObject> map = new HashMap<>();
map.put(key(a, b, c), myObject);

我还没有测量 key() 方法的性能,但只有几个 CPU 周期。