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

2019-01-23 23:37发布

问题:

I am creating a deep clone for some object. The object contains a Random.

Is it good practice to retrieve the seed from the Random? If so, how? There isn't a Random.getSeed().

回答1:

A Random is intended to be random. Usually you want two Random to produce different numbers rather than to produce the same numbers.

You can copy a Random using serialisation/de-serialisation and get the "seed" field using reflection. (But I doubt you should be doing either)

Unless the sequence is critical to you, you can take the view that the clone of a Random is itself or any new Random()



回答2:

What you can do is get the system time yourself, then seed the random number generator with it and store it somewhere or print it so you can use it later.

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


回答3:

A much more simple way of getting a seed is to generate one and store that as the seed. I am using this method for a game and want to give the player the option to generate the exact same world if he wishes too. So first I create a Random object without a seed then let that one generate a random number and use that in another random object as the seed. Whenever the player want the seed of the level I have it stored somewhere. By default the game is still 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);


回答4:

It can be a good practice depending on your purpose. For most purposes you don't need to retrieve the current seed. For example if your purpose is to have two Random generators which generate the same sequence of values, then you don't need to retrieve the random seed: you just create those two Random objects with the same (pre-set) seed.

Java doesn't provide a standard way of retrieving the seed from a Random object. If you really need that number, you may work around it: serialize your Random object, serialize another Random object (with a different seed), find the 8 bytes where these two strings differ, and retrieve the seed value from those 8 bytes.

Here is how to do it with serialization:

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.");
  }
}


回答5:

This can be done via reflection, although there is a small quirk:

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

The magic number 0x5DEECE66DL comes from the source code of Random.java, where seeds are given an "initial scramble" of:

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

which XOR's them with a random number and truncates them to 48 bits. Thus, to recreate the seed state, we have to XOR the seed we pulled.



回答6:

Interesting paradox... I would not call the cloned Random object random - as a workaround you could try this: when you're cloning your object you can set seed by yourself in both Random instances with the same value.



回答7:

The reason I'm here is that I need to remember the seed in case I need to recreate what happened in a particular program run. The code I used is:

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

Although this isn't quite what is asked for, I think it is probably what is required.