根据哈希创建一个统一的随机数
我需要一个基于由字符串和长整数组成的密钥的良好伪随机数。 当我使用相同的密钥查询时,我应该得到相同的随机数,而且,如果我使用略微不同的密钥进行查询,我应该得到一个非常不同的数字,即使说密钥中的长度是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
部分的小变化显示出更多的变化。