我正在为某个对象创建一个深度克隆。该对象包含一个Random
.
从 中检索种子是一种好习惯Random
吗?如果是这样,怎么做?没有一个Random.getSeed()
。
我正在为某个对象创建一个深度克隆。该对象包含一个Random
.
从 中检索种子是一种好习惯Random
吗?如果是这样,怎么做?没有一个Random.getSeed()
。
获取种子的一种更简单的方法是生成一个并将其存储为种子。我正在将这种方法用于游戏,并希望让玩家可以选择生成完全相同的世界,如果他也愿意的话。因此,首先我创建一个没有种子的 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);
您可以做的是自己获取系统时间,然后用它为随机数生成器播种并将其存储在某处或打印出来,以便以后使用。
long rgenseed = System.currentTimeMillis();
Random rgen = new Random();
rgen.setSeed(rgenseed);
System.out.println("Random number generator seed is " + rgenseed);
根据您的目的,这可能是一个很好的做法。大多数情况下,您不需要检索当前种子。例如,如果您的目的是让两个 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.");
}
}
这可以通过反射来完成,尽管有一个小怪癖:
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 位。因此,要重新创建种子状态,我们必须对我们提取的种子进行异或。
A Random 旨在是随机的。通常,您希望两个 Random 产生不同的数字,而不是产生相同的数字。
您可以使用序列化/反序列化复制 Random 并使用反射获取“种子”字段。(但我怀疑你应该这样做)
除非序列对您很重要,否则您可以认为 Random 的克隆是它本身或任何new Random()
有趣的悖论......我不会将克隆的Random
对象称为随机- 作为一种解决方法,您可以尝试以下方法:当您克隆对象时,您可以在两个Random
实例中自行设置具有相同值的种子。
我在这里的原因是我需要记住种子,以防我需要重新创建在特定程序运行中发生的事情。我使用的代码是:
long seed = random.nextLong();
random.setSeed(seed);
尽管这不是所要求的,但我认为这可能是所需要的。