根据哈希创建一个统一的随机数

我需要一个基于由字符串和长整数组成的密钥的良好伪随机数。 当我使用相同的密钥查询时,我应该得到相同的随机数,而且,如果我使用略微不同的密钥进行查询,我应该得到一个非常不同的数字,即使说密钥中的长度是1,我试过这个代码并且随机数是唯一的,但对于相似的数字,它们似乎是相关的。

import java.util.Date; import java.util.Random; import org.apache.commons.lang3.builder.HashCodeBuilder; public class HashKeyTest { long time; String str; public HashKeyTest(String str, long time) { this.time = time; this.str = str; } @Override public int hashCode() { return new HashCodeBuilder().append(time).append(str).toHashCode(); } public static void main(String[] args) throws Exception { for(int i=0; i<10; i++){ long time = new Date().getTime(); HashKeyTest hk = new HashKeyTest("SPY", time); long hashCode = (long)hk.hashCode(); Random rGen = new Random(hashCode); System.out.format("%d:%d:%10.12f\n", time, hashCode, rGen.nextDouble()); Thread.sleep(1); } } } 

解决方案我拼凑在一起。 这很好用,但我想知道它是否需要这个冗长。

 import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; public class HashKeyTest implements Serializable{ long time; String str; public HashKeyTest(String str, long time) { this.time = time; this.str = str; } public double random() throws IOException, NoSuchAlgorithmException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(this); byte[] bytes = bos.toByteArray(); MessageDigest md5Digest = MessageDigest.getInstance("MD5"); byte[] hash = md5Digest.digest(bytes); ByteBuffer bb = ByteBuffer.wrap(hash); long seed = bb.getLong(); return new Random(seed).nextDouble(); } public static void main(String[] args) throws Exception { long time = 0; for (int i = 0; i < 10; i++) { time += 250L; HashKeyTest hk = new HashKeyTest("SPY", time); System.out.format("%d:%10.12f\n", time, hk.random()); Thread.sleep(1); } } } 

你说“当我使用相同的密钥查询时,我应该得到相同的随机数,而且,如果我使用稍微不同的密钥查询,我应该得到一个非常不同的数字”。 如果我正确理解您的问题,您不需要随机数,而是需要加密哈希码。

您应该通过SHA或MD5等哈希函数来查看传递的数据。 这将给你一些看似随机的输入,但在相同的输入下总是相同的,并且即使你的输入变化很小,也会变化很大。

编辑:要始终获得双值,尝试这样的事情(伪代码):

 SHAHashValue v = ComputeSHA( yourObject); Random r = new Random(v); the_random_value = r.getNext(); 

这里的想法是使用SHA哈希值作为种子来初始化随机生成器。 这几乎就是你所拥有的,但我不知道你的HashBuilder在不同的值方面产生了什么。 因此,使用SHA哈希可能会改善这种情况。

您还应该考虑0到1之间的双精度“非常不同”的值可能不会立即显现出来。

我只想使用密钥的哈希本身作为“随机”数字。 假设一个合理的哈希实现,它将具有你提到的所有属性。

这是一个有点令人惊讶的结果。 我原以为种子中的一个小差异应该会导致随机数流的巨大差异。 经过反思,我不知道为什么我这么想。

不过,它很容易修复!

也许最简单的事情就是让随机数发生器在使用前稍微加热一下。 由不同种子产生的比特流开始时相似,但发散很快,所以简单地丢弃比特流的早期部分应该可以完成工作。 在创建Random的行之后,立即添加:

 rGen.nextLong(); 

或者,更多的分歧:

 for (int j = 0; j < 10; ++j) rGen.nextLong(); 

快速测试表明,这会得到更多种类的数字。

另一种选择是使用java.security.SecureRandom作为随机数生成器。 这样可以更好地从类似输入生成不同的输出。 你用一个字节数组播种它; 你可以通过说(str + time).getBytes()类的东西来产生一个。

另一种选择是获取种子,然后使用加密散列(如SHA-256)对其进行散列,然后将其中的一部分用作种子。 散列将采用非常相似的输入并产生非常不同的输出,这将为您提供适当不同的随机比特流。

我的理解是:

  • 您的对象有两个实例变量 – 长时间和字符串str ,需要将其考虑在内以计算随机数
  • 您希望随机数对time部分非常敏感。
  • time + str组合应产生相同的随机数。
  • 如果两个不同的time + str组合产生相同的随机数,则可以。

从你发布的代码来看,似乎HashCodeBuilder()并不像你想要的那样灵敏。

除了别人的建议,一个想法可能是以一致的方式改变time本身。

您可以取最后一位数字(键的long部分)并将其移动到数字中间的某个位置。 例如,你的hashCode()可以是:

 @Override public int hashCode() { return (new org.apache.commons.lang.builder.HashCodeBuilder() .append(time+((time%10)*100000000)).append(str).toHashCode()); } 

(代码并没有完全将最后一个数字移到中间,而是在问题的上下文中做了类似的事情)

但这会有点慢。 所以你可以将它转换为位运算符。

 @Override public int hashCode() { return (new org.apache.commons.lang.builder.HashCodeBuilder() .append(time+((time & 63l) << 57)).append(str).toHashCode()); } 

有点像提取最后6位时间( time & 63l )并将这些位置于前面( 57是随机的。我只是想将这些位移动到更重要的位置)。 这与“将数字移动到中间的某个地方”完全不符,但与概念上类似。

如果仅提取最后5位( time & 31l ),您将获得更多方差。 你可以尝试不同的价值观。 对于问题中发布的代码, time & 63l版本返回以下输出:

 1339343005559:-1084202043:0.339762681480 1339343005585:1801482883:0.323979029483 1339343005586:559968862:0.786162684846 1339343005587:-681545159:0.241820545267 1339343005588:-580881900:0.692788956755 1339343005590:1231057354:0.624686671170 1339343005591:-10456667:0.530394885899 1339343005592:1700819920:0.894868466104 1339343005593:459305899:0.149584882259 1339343005595:-2023722143:0.289584988289 

正如预期的那样,这对于密钥的long部分的小变化显示出更多的变化。