如何从Java中随机获取种子?

How do I get the seed from a Random in Java?

我正在为某个对象创建深度克隆。对象包含一个Random

Random中取出种子是一种好的做法吗?如果是这样,怎么办?没有Random.getSeed()


你所能做的就是自己获得系统时间,然后用它来输入随机数生成器,并将其存储在某个地方或打印出来,以便以后使用。

1
2
3
4
long rgenseed = System.currentTimeMillis();
Random rgen = new Random();
rgen.setSeed(rgenseed);
System.out.println("Random number generator seed is" + rgenseed);


获取种子的一个更简单的方法是生成一个种子并将其存储为种子。我在游戏中使用这种方法,如果玩家愿意的话,我想让他选择生成完全相同的世界。因此,首先我创建一个没有种子的随机对象,然后让它生成一个随机数,并在另一个随机对象中使用它作为种子。每当玩家想要这个关卡的种子,我就把它储存在某个地方。默认情况下,游戏仍然是随机的。

1
2
3
4
5
6
7
8
9
10
    Random rand = new Random();
    //Store a random seed
    long seed = rand.nextLong();
    //Set the Random object seed
    rand.setSeed(seed);

    //do random stuff...

    //Wonder what the seed is to reproduce something?
    System.out.println(seed);


这可能是一个好的实践,取决于你的目的。在大多数情况下,您不需要检索当前种子。例如,如果您的目的是让两个随机生成器生成相同的值序列,那么您不需要检索随机种子:您只需使用相同(预设)种子创建这两个随机对象。

Java不提供从随机对象中检索种子的标准方法。如果您真的需要这个数字,您可以处理它:序列化您的随机对象,序列化另一个随机对象(使用不同的种子),找到这两个字符串不同的8个字节,并从这8个字节中检索种子值。

下面是如何进行序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Random;
public class SeedGetter {
  static long getSeed(Random random) {
    byte[] ba0, ba1, bar;
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(new Random(0));
      ba0 = baos.toByteArray();
      baos = new ByteArrayOutputStream(128);
      oos = new ObjectOutputStream(baos);
      oos.writeObject(new Random(-1));
      ba1 = baos.toByteArray();
      baos = new ByteArrayOutputStream(128);
      oos = new ObjectOutputStream(baos);
      oos.writeObject(random);
      bar = baos.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException("IOException:" + e);
    }
    if (ba0.length != ba1.length || ba0.length != bar.length)
      throw new RuntimeException("bad serialized length");
    int i = 0;
    while (i < ba0.length && ba0[i] == ba1[i]) {
      i++;
    }
    int j = ba0.length;
    while (j > 0 && ba0[j - 1] == ba1[j - 1]) {
      j--;
    }
    if (j - i != 6)
      throw new RuntimeException("6 differing bytes not found");
    // The constant 0x5DEECE66DL is from
    // http://download.oracle.com/javase/6/docs/api/java/util/Random.html .
    return ((bar[i] & 255L) << 40 | (bar[i + 1] & 255L) << 32 |
            (bar[i + 2] & 255L) << 24 | (bar[i + 3] & 255L) << 16 |
            (bar[i + 4] & 255L) << 8 | (bar[i + 5] & 255L)) ^ 0x5DEECE66DL;
  }
  public static void main(String[] args) {
    Random random = new Random(12345);
    if (getSeed(random) != 12345)
      throw new RuntimeException("Bad1");
    random.nextInt();
    long seed = getSeed(random);
    if (seed == 12345)
      throw new RuntimeException("Bad2");
    Random random2 = new Random(seed);
    if (random.nextInt() != random2.nextInt())
      throw new RuntimeException("Bad3");
    System.out.println("getSeed OK.");
  }
}


随机的目的是随机的。通常,您希望两个随机数产生不同的数字,而不是产生相同的数字。

您可以使用串行化/取消串行化来复制随机数据,并使用反射来获取"seed"字段。(但我怀疑你也应该这样做)

除非序列对您至关重要,否则您可以认为随机数的克隆是其自身或任何new Random()


这可以通过反射来实现,尽管有一个小的怪癖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Random r = ...;  //this is the random you want to clone
long theSeed;
try
{
    Field field = Random.class.getDeclaredField("seed");
    field.setAccessible(true);
    AtomicLong scrambledSeed = (AtomicLong) field.get(r);   //this needs to be XOR'd with 0x5DEECE66DL
    theSeed = scrambledSeed.get();
}
catch (Exception e)
{
    //handle exception
}
Random clonedRandom = new Random(theSeed ^ 0x5DEECE66DL);

神奇的数字0x5DEECE66DL来自random.java的源代码,在该代码中,种子被赋予"初始扰码",即:

1
2
3
4
5
6
private static final long multiplier = 0x5DEECE66DL;
private static final long mask = (1L << 48) - 1;
//...
private static long initialScramble(long seed) {
    return (seed ^ multiplier) & mask;
}

其中xor是随机数,并将其截断为48位。因此,为了重新创建种子状态,我们必须XOR我们提取的种子。


有趣的悖论…我不会将克隆的Random对象称为随机的——作为一种解决方法,您可以尝试这样做:当您克隆对象时,您可以在两个具有相同值的Random实例中自己设置种子。


我来这里的原因是我需要记住种子,以防我需要重新创建在特定程序运行中发生的事情。我使用的代码是:

1
2
long seed = random.nextLong();
random.setSeed(seed);

虽然这不完全是要求的,但我认为这可能是要求的。