4

我需要一个好的伪随机数,它基于一个由字符串和一个 long 组成的密钥。当我使用相同的密钥进行查询时,我应该得到相同的随机数,而且,如果我使用稍微不同的密钥进行查询,我应该得到一个非常不同的数字,即使说密钥中的 long 为 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

4 回答 4

2

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

于 2012-06-10T15:44:50.187 回答
2

这是一个有点令人惊讶的结果。我会认为种子的微小差异会导致随机数流的巨大差异。回想起来,我不知道为什么我会这么想。

不过,它很容易修复!

也许最简单的事情就是让随机数生成器在使用之前先预热一下。不同种子产生的比特流开始时相似,但分歧很快,因此只需丢弃比特流的早期部分就可以了。在创建 的行之后,立即Random添加:

rGen.nextLong();

或者,为了更多的分歧:

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

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

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

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

于 2012-06-10T15:46:11.707 回答
2

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

您应该考虑通过 SHA 或 MD5 等哈希函数传递您拥有的任何数据。这会给你一些看似随机的输入,但在相同的输入下总是相同的,即使你的输入变化很小,也会有很大的不同。

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

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

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

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

于 2012-06-10T15:58:19.767 回答
0

我的理解是:

  • 您的对象有两个实例变量 - 一个 longtime和一个字符串str,需要考虑计算随机数
  • 您希望随机数对time零件非常敏感。
  • 相同的time+str组合应该产生相同的随机数。
  • 如果两个不同的time+str组合产生相同的随机数是可以的。

从您发布的代码来看,它似乎HashCodeBuilder()并不像您希望的那样敏感time

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

您可以取timelong键的部分)的最后一位数字并将其移动到数字中间的某个位置。例如,您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正如预期的那样,对于密钥部分的微小变化,它显示出更大的差异。

于 2012-06-10T15:32:41.603 回答