如何在Java中获取Random的种子?

36

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

Random 中检索种子是一种好的做法吗?如果是,应该如何实现?Random 没有提供 getSeed() 方法。


2
我提供了一种获取Random对象(或其子类)的干净副本的方法,因此我派生了一个类CopyableRandom extends Random implements Copyable<CopyableRandom>。https://dev59.com/oHbZa4cB1Zd3GeqPDDbl#18531276 - mike
7个回答

25

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

    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);

1
这可以用于创建具有相同状态的两个随机数。在克隆时,使用rand.nextLong()生成一个种子,并使用该种子为原始随机数和其副本设置种子。从那时起,原始随机数和副本都会生成相同的数字。 - Markus Klinik

10
这可以通过反射完成,但有一个小问题:
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源代码,其中种子被赋予“初始混乱”:

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

该算法使用一个随机数对种子进行异或操作,并将结果截取为48位。因此,为了重新创建种子状态,我们需要对提取的种子执行异或操作。


9
你可以自己获取系统时间,然后使用它来生成随机数并将其存储在某个位置或打印出来以便以后使用。
long rgenseed = System.currentTimeMillis();
Random rgen = new Random();
rgen.setSeed(rgenseed);
System.out.println("Random number generator seed is " + rgenseed);

12
随着您继续使用随机实例,种子内部会发生变化。 - mike
这只会让你从头开始重新启动序列,而不是在原地恢复它。 - Chris Westin

8
根据您的目的,这可能是一种不错的做法。对于大多数情况,您不需要检索当前种子值。例如,如果您的目的是拥有两个生成相同值序列的Random生成器,则无需检索随机种子:只需使用相同(预设)种子创建这两个Random对象即可。
Java没有提供从Random对象检索种子的标准方式。如果您确实需要该数字,可以通过以下方法解决:序列化您的Random对象,序列化另一个Random对象(具有不同的种子),找到这两个字符串不同的8个字节,并从这些8个字节中检索种子值。
以下是如何使用序列化执行此操作:
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.");
  }
}

看一下我的解决方案。在那里获取种子更容易。https://dev59.com/oHbZa4cB1Zd3GeqPDDbl#18531276 - mike

5

随机数应该是随机的。通常情况下,您希望两个 Random 产生不同的数字,而不是产生相同的数字。

您可以使用序列化/反序列化复制 Random,并使用反射获取 "seed" 字段。(但我怀疑您是否应该这样做)

除非序列很重要,否则您可以认为 Random 的克隆是它本身或任何 new Random()


14
很抱歉打扰了过往的讨论,但至少我能想到一种不那么晦涩的应用场景需要在多次执行中保留随机数生成器的顺序:视频游戏,尤其是策略游戏,其中“厄运”是玩家应该管理的一部分。一个著名的例子是《异形战争》原始版本保存了随机生成器状态以防止玩家在每个士兵射击前快速保存并重新加载如果没有命中。(扩展包有一个配置可以明确启用“存档作弊”) - millimoose
在这种情况下,您可以保持重新播种随机数,即每秒甚至每100毫秒一次,这样您就可以重放最后100毫秒+。 - Peter Lawrey
2
这对于存档作弊无济于事——玩家可以根据进行操作的100毫秒间隔获得不同的结果,这在所有情况下都没有任何意义。在一个强调长期策略而非赌博的视频游戏中,允许保存和加载,你不希望阻止玩家知道下一次“掷骰子”的结果。相反,你希望玩家不能逃避命运,并且每次都能“重新掷骰子”,直到结果有利。(例如,重复加载直到你的士兵命中,或者直到外星人错过。) - millimoose
3
另一个有用的情况是在使用随机数生成器生成测试数据时。我编写测试,使它们设置种子,以便每次运行都相同;否则,我无法重现发现的任何错误。还可以找到当前种子值,以便可以从离开的地方恢复序列,这也很有用。 - Chris Westin
2
有很多情况下,你想要使用相同的种子重新创建一个随机对象。不好的答案是:你只需要使用random.nextLong()作为种子来初始化Random,并记下它。 - Jack

3

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


1

我在这里的原因是,为了以后需要重现特定程序运行时所发生的事情,我需要记住种子。我使用的代码如下:

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

尽管这不完全是所要求的,但我认为这可能是所需的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接