哈希:比Object.hashCode()
提供的更复杂的哈希工具,包括布隆过滤器。
Java固有的哈希码概念被限制为32位,并且在哈希算法和它们所作用的数据之间没有分隔,因此替代的哈希算法不容易被替换。同样,hashCode的实现往往质量较差,部分原因是它们最终依赖于其他现有的质量较差的hashCode实现,包括许多JDK类中的实现。
Object.hashCode实现通常非常快,但是具有较弱的冲突预防功能,并且不期望出现位分散。这使它们非常适合在哈希表中使用,因为额外的冲突只会对性能造成轻微的影响,而不良的位分散则可以使用辅助哈希函数(使用Java中所有合理的哈希表实现)轻松纠正。然而,对于哈希函数在简单哈希表之外的许多用途,Object.hashCode
几乎总是不足——因此有了com.google.common.hash
。
在查看Javadoc包时,我们看到了许多不同的类型,但是它们如何组合在一起并不明显。
让我们来看一段使用该库的示例代码。
HashFunction hf = Hashing.md5();
HashCode hc = hf.newHasher()
.putLong(id)
.putString(name, Charsets.UTF_8)
.putObject(person, personFunnel)
.hash();
HashFunction
是一个纯无状态函数,它将任意数据块映射到固定数量的位,其属性是相等的输入始终产生相等的输出,而不相等的输入则尽可能每次产生不相等的输出。
可以向HashFunction
请求有状态的Hasher
,它提供了流利的语法以将数据添加到哈希中,然后检索哈希值。Hasher
可以接受任何原生输入、字节数组、字节数组的片段、字符序列、某些字符集中的字符序列等,或任何其他带有合适的Funnel
的Object
。
Hasher
实现了PrimitiveSink
接口,该接口为接受原生值流的对象指定了流利的API。
Funnel
描述了如何将特定的对象类型分解为原生字段值。例如,如果我们有
class Person {
final int id;
final String firstName;
final String lastName;
final int birthYear;
}
我们的Funnel
看起来像
Funnel<Person> personFunnel = new Funnel<Person>() {
@Override
public void funnel(Person person, PrimitiveSink into) {
into
.putInt(person.id)
.putString(person.firstName, Charsets.UTF_8)
.putString(person.lastName, Charsets.UTF_8)
.putInt(birthYear);
}
};
注意:putString("abc", Charsets.UTF_8).putString("def", Charsets.UTF_8)
完全等效于putString("ab", Charsets.UTF_8).putString("cdef", Charsets.UTF_8)
,因为它们产生了相同的字节序列。这可能会导致意外的哈希冲突。添加某种分隔符可以帮助消除意外的哈希冲突。
一旦为Hasher
提供了所有输入,就可以使用其hash()
方法检索HashCode
。HashCode
支持相等性测试等,以及asInt()
、asLong()
、asBytes()
方法,还支持writeBytesTo(array, offset, maxLength)
,它将哈希的第一个maxLength
字节写入数组。
布隆过滤器是一个很好的哈希应用程序,不能简单地使用Object.hashCode()
来完成。简而言之,布隆过滤器是一种概率数据结构,可让你测试某个对象是否绝对不在过滤器中,或者可能已添加到Bloom过滤器中。维基百科页面非常全面,我们推荐使用本教程。
我们的哈希库具有内置的布隆过滤器实现,只需要你实现一个Funnel
即可将你的类型分解为原生类型。你可以使用create(Funnel funnel, int expectedInsertions, double falsePositiveProbability)
获得新的BloomFilter
,或者只接受默认的3%错误概率。BloomFilter
提供了方法boolean mightContain(T)
和void put(T)
,这已经不言而喻了。
BloomFilter<Person> friends = BloomFilter.create(personFunnel, 500, 0.01);
for (Person friend : friendsList) {
friends.put(friend);
}
// much later
if (friends.mightContain(dude)) {
// the probability that dude reached this place if he isn't a friend is 1%
// we might, for example, start asynchronously loading things for dude while we do a more expensive exact check
}
Hashing
工具类提供了许多用于对HashCode
对象进行操作的签名哈希函数和工具。
md5()
murmur3_128()
murmur3_32()
sha1()
sha256()
sha512()
goodFastHash(int bits)
方法 | 描述 |
---|---|
HashCode combineOrdered(Iterable) |
以有序方式组合哈希码,因此,如果从此方法获得的两个哈希相同,则很可能每个哈希都是以相同的顺序从相同的哈希中计算得出的。 |
HashCode combineUnordered(Iterable) |
以无序方式组合哈希码,因此,如果从此方法获得的两个哈希相同,则很可能每个哈希都是以相同的顺序从相同的哈希中计算得出的。 |
int consistentHash(HashCode, int buckets) |
为哈希码分配一个一致的“桶”,这样可以最大限度地减少随着bucket桶数量的增加而重新映射的需要。有关详细信息,请参见维基百科。 |
本文参考:
HashingExplained
guava-tests-hash