二进制数据

时间:2016-05-03 18:40:22

标签: javascript java scala hashcode

我正在寻找一种便携式算法,用于为二进制数据创建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,否则我会得到相同的结果。

2 个答案:

答案 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的版本是错误的......)