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

时间:2012-06-10 15:00:27

标签: java random apache-commons-math

我需要一个基于由字符串和长整数组成的密钥的良好伪随机数。当我使用相同的密钥查询时,我应该得到相同的随机数,而且,如果我使用略微不同的密钥查询,我应该得到一个非常不同的数字,即使说密钥中的长度是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);
        }
    }
}

4 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

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

不过,它很容易修复!

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

rGen.nextLong();

或者,更多的分歧:

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

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

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

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

答案 2 :(得分:2)

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

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

编辑: 要始终如一地获得双值,请尝试这样的(伪代码):

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

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

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

答案 3 :(得分:0)

我的理解是:

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

根据您发布的代码,似乎HashCodeBuilder()不像您希望的那样敏感time

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

您可以取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部分的小变化显示了更多的差异。