我正在为一个对象创建深度克隆。该对象包含一个 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);
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位。因此,为了重新创建种子状态,我们需要对提取的种子执行异或操作。
long rgenseed = System.currentTimeMillis();
Random rgen = new Random();
rgen.setSeed(rgenseed);
System.out.println("Random number generator seed is " + rgenseed);
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 产生不同的数字,而不是产生相同的数字。
您可以使用序列化/反序列化复制 Random,并使用反射获取 "seed" 字段。(但我怀疑您是否应该这样做)
除非序列很重要,否则您可以认为 Random 的克隆是它本身或任何 new Random()
有趣的悖论...我不会称克隆的Random
对象为随机的- 作为解决方法,您可以尝试这样做:在克隆对象时,您可以使用相同的值在两个Random
实例中自己设置种子。
我在这里的原因是,为了以后需要重现特定程序运行时所发生的事情,我需要记住种子。我使用的代码如下:
long seed = random.nextLong();
random.setSeed(seed);
CopyableRandom extends Random implements Copyable<CopyableRandom>
。https://dev59.com/oHbZa4cB1Zd3GeqPDDbl#18531276 - mike