如果您不关心哈希算法是否具有密码质量(密码哈希算法很难正确指定;您搞砸了,当您不希望他们这样做时,有人可能会导致冲突),以下应该工作:
考虑以下代码:
interface Accumulator<T, U>
{
public void add(T t);
public void subtract(T t);
public U get();
}
class SumHasher implements Accumulator<String,Integer>
{
@Override private int accumulator = 0;
@Override public void add(String t) { accumulator += t.hashCode(); }
@Override public void subtract(String t) { accumulator -= t.hashCode(); }
@Override public Integer get() { return accumulator; }
}
class XorHasher implements Accumulator<String,Integer>
{
@Override private int accumulator = 0;
@Override public void add(String t) { accumulator ^= t.hashCode(); }
@Override public void subtract(String t) { accumulator ^= t.hashCode(); }
@Override public Integer get() { return accumulator; }
}
它们的共同点是加法和异或都是关联并具有逆的操作。您可以按任何顺序执行它们并按任何顺序撤消它们,因此如果您对Set<T> 中的每个元素add(),然后对集合中的每个元素subtract()(不一定以相同的顺序),您保证得到0。
当然还有其他操作可以满足此属性,但我不确定它们是什么。 (乘法将不起作用,除非您可以保证累积的所有项目的值都不为 0。这个答案曾经使用 f(x,h) = ((x^h) + h)^h 和 g(x,h ) = ((x^h) - h)^h 作为逆函数,但这些函数不是关联的:以不同顺序累加元素会产生不同的结果。
编辑:我确实想到了另一个简单的方法:基于输入值的按位排列(其中按位旋转是一种特殊情况)。在 Java 中,您可以使用 (x << k) | (x >>> (32-k)) 实现按位旋转,其中 x 是整数,k 是 0 到 31 之间的整数(例如,从另一个数字中取任意 5 位)。 >>> 不是错字:您需要使用它,因为常规的 >> 会进行符号扩展。 糟糕,只有在集合中的元素被删除时才有效倒序。
编辑 2:最后,您可以更一般地实现此方法,如下所示:
abstract class AbstractHashCodeAccumulator<T> implements Accumulator<T, Integer>
{
private int accumulator = 0;
abstract protected int combine(int a, int h);
abstract protected int uncombine(int a, int h);
@Override public void add(T t) { accumulator = combine(accumulator, t.hashCode());
@Override public void subtract(T t) { accumulator = uncombine(accumulator, t.hashCode());
@Override public Integer get() { return accumulator; }
}
class SumHasher extends AbstractHashCodeAccumulator<String>
{
@Override protected int combine(int a, int h) { return a+h; }
@Override protected int uncombine(int a, int h) { return a-h; }
}
class XorHasher extends AbstractHashCodeAccumulator<String>
{
@Override protected int combine(int a, int h) { return a^h; }
@Override protected int uncombine(int a, int h) { return a^h; }
}
这种方法的问题在于,在某些方面它是“非散列式的”,即它需要有序,而散列通常需要无序/熵/不可逆性。