我正在寻找一种便携式算法,用于为二进制数据创建hashCode。二进制数据都不是很长 - 我是Avro
- 在kafka.KeyedMessages
中使用的编码密钥 - 我们可能在说长度为2到100个字节的任何地方,但大多数密钥都是在4到8个字节范围内。
到目前为止,我最好的解决方案是将数据转换为十六进制字符串,然后执行hashCode
。我能够在Scala和JavaScript中完成这项工作。假设我已定义b: Array[Byte]
,Scala看起来像这样:
b.map("%02X" format _).mkString.hashCode
在JavaScript
中稍微详细一点 - 幸运的是 someone already ported JavaScript的基本hashCode算法 - 但重点是能够创建{{1} } string表示二进制数据,我可以确保散列算法在相同的输入下工作。
另一方面,我必须创建一个两倍于原始大小的对象才能创建hashCode。幸运的是,我的大多数数据都很小,但仍然 - 必须有更好的方法来实现这一点。
不是将数据填充为十六进制值,而是假设您可以将二进制数据强制转换为String,因此String与二进制数据具有相同的字节数。它将是所有乱码,比可打印字符更多的控制字符,但它仍然是一个字符串。你是否遇到了可移植性问题? Endian-ness,Unicode等
顺便说一下,如果你有这么远的阅读并且还不知道这一点 - 你不能这样做:
Hex
幸运的是,在我开始之前我已经知道了,因为我很早就碰到了那个。
更新
根据给出的第一个答案,乍一看,val b: Array[Byte] = ...
b.hashCode
可以解决问题。但是,如果你按照javadoc跟踪,你会看到这是它背后的算法,它基于List的算法和java.util.Arrays.hashCode(Array[Byte])
组合的算法。
byte
如您所见,它所做的只是创建一个代表值的int hashCode = 1;
for (byte e : list) hashCode = 31*hashCode + (e==null ? 0 : e.intValue());
。在某个时刻,这个数字太大了,它就会变得很糟糕。这不是很便携。我可以让它适用于JavaScript,但您必须导入Long
模块npm
。如果你这样做,它看起来像这样:
long
当数据包裹时,你会得到相同的结果,但我不知道为什么。在斯卡拉:
function bufferHashCode(buffer) {
const Long = require('long');
var hashCode = new Long(1);
for (var value of buff.values()) { hashCode = hashCode.multiply(31).add(value) }
return hashCode
}
bufferHashCode(new Buffer([1,2,3]));
// hashCode = Long { low: 30817, high: 0, unsigned: false }
请注意,结果是Int。在JavaScript中:
java.util.Arrays.hashCode(Array[Byte](1,2,3,4,5,6,7,8,9,10))
// res30: Int = -975991962
所以我必须取bufferHashCode(new Buffer([1,2,3,4,5,6,7,8,9,10]);
// hashCode = Long { low: -975991962, high: 197407, unsigned: false }
个字节并忽略low
,否则我会得到相同的结果。
答案 0 :(得分:1)
此功能已在Java标准库中提供,请查看Arrays.hashCode()
方法。
因为您的二进制数据是Array[Byte]
,所以您可以通过以下方式验证其是否有效:
println(java.util.Arrays.hashCode(Array[Byte](1,2,3))) // prints 30817
println(java.util.Arrays.hashCode(Array[Byte](1,2,3))) // prints 30817
println(java.util.Arrays.hashCode(Array[Byte](2,2,3))) // prints 31778
更新: Java实现不包含字节。当然,有转换为int,但没有办法解决这个问题。这是Java实现:
public static int hashCode(byte a[]) {
if (a == null) return 0;
int result = 1;
for (byte element : a) result = 31 * result + element;
return result;
}
更新2 如果您需要的是一个JavaScript实现,它提供与Scala / Java实现相同的结果,那么您可以扩展算法,例如,仅采用最右边的31位:
def hashCode(a: Array[Byte]): Int = {
if (a == null) {
0
} else {
var hash = 1
var i: Int = 0
while (i < a.length) {
hash = 31 * hash + a(i)
hash = hash & Int.MaxValue // taking only the rightmost 31 bits
i += 1
}
hash
}
}
和JavaScript:
var hashCode = function(arr) {
if (arr == null) return 0;
var hash = 1;
for (var i = 0; i < arr.length; i++) {
hash = hash * 31 + arr[i]
hash = hash % 0x80000000 // taking only the rightmost 31 bits in integer representation
}
return hash;
}
为什么这两个实现产生相同的结果?在Java中,整数溢出的行为就像执行加法而不会丢失精度一样,然后高于32的位被抛弃,& Int.MaxValue
抛弃32 nd 位。在JavaScript中,最多2 53 的整数不会丢失精度,这是表达式31 * hash + a(i)
永远不会超过的限制。 % 0x80000000
然后表现为最右边的31位。没有溢出的情况很明显。
答案 1 :(得分:1)
这是Java库中使用的算法的基础:
int result 1;
for (byte element : a) result = 31 * result + element;
你发表评论:
这个算法不是很便携
不正确的。如果我们谈论Java,那么只要我们都同意result
的类型,那么该算法是100%可移植的。
是的,计算溢出,但它在所有有效的Java语言实现上以完全相同的方式溢出 。 Java int
指定是32位有符号的二进制补码,溢出时运算符的行为是明确定义的...并且对于所有实现都是相同的。 (同样适用于long
......虽然规模不同,但很明显。)
我不是专家,但我的理解是Scala的数字类型与Java具有相同的属性。 Javascript是不同的,基于IEE 754双精度浮点。但是,在大多数情况下,您应该能够在Javascript中以可移植的方式编写Java算法。 (我认为@Mifeet的版本是错误的......)