Random这个东西非常好玩,Random的seed
使用的就是AtomicLong类型,AtomicLong的value使用的volatile
关键字可以避免读取出寄存器中的脏数据,而数值更新时使用的Unsafe内存操作极其高效,完美的避免了synchronized
的性能负担,这在Random.next()
运作时非常重要,因为如果两次使用了同样的seed
,在next()
得到的随机结果必然相同。
好玩的地方在于,这个seed是可以设置的,不但在construction
中可以自定义seed,也可以使用setSeed(long seed)
切换到新的seed,这一点是很好理解的,毕竟将System.nanoTime()
和一些魔法数字混合属于伪随机,Random将seed
作为只写属性,为Random的真随机留了一扇门。
如果想生成一些奇怪的数字,random是再合适不过的了,只要有合适的seed与一个合适的bound,它几乎能得到任何范围内的值,我决定将bound设为10000,检索0到0xffff间的seed。
for (int i = 0; i < 0xffff; i++) {
Random r = new Random(i);
if (r.nextInt(10000) == 2021) {
System.out.println(i);
}
}
3318
14001
24132
25562
30727
46376
50878
由于范围小几乎秒出结果,这些seed都是合适的
使用不同的bound也可以得出合适的结果
for (int i = 0; i < 0xffff; i++) {
Random r = new Random(1);
if (r.nextInt(10000 + i) == 2021) {
System.out.println(i);
}
}
6286
22572
38187
以下两种方式都能得到2021
new Random(3318).nextInt(10000)
new Random(1).nextInt(16286)
不断的进行nextInt()
可以得到更多数字,将seed置为0,round设在10000,经过不断nextInt()
依然可以得到2021
Random r = new Random(1);
for (int i = 0; i < 15683; i++) {
r.nextInt();
}
System.out.println(r.nextInt(10000));
//2021
注意: round不影响下一次随机时的seed。seed每次自动更新,都是由旧seed进行以下计算得出的
((oldseed * 0x5DEECE66DL + 0xBL) & 0xffffffffffff
现有的这些数字,都是一些杂乱数据,不美观,我扩大范围,将
seed
设为0xffff
,由于0xffff
这个范围对于2021还是太大,难以检索出有趣的数字,我右移一位,使round
为实际值为0x7fff
,进行海量的检索,找到了两个看上去很好玩的数字42174 47124
看到这个突然眼前一亮,这两个数字互相倒置,循环数量达到这两个数字都会得到2021,我直接检索这种规则的数字,看看除了2021还有哪些
final int seed = 0xffff; final int round = seed >> 1; for (int i = 0; i < seed; i++) { Random foo = new Random(seed); Random bar = new Random(seed); int reverse = 0; int temp = i; while (temp != 0) { reverse = reverse * 10 + (temp % 10); temp /= 10; } //i % 10 == 0用来排除个位数的情况 if (i == reverse || i % 10 == 0) { continue; } IntStream.range(0, i).forEach(e -> foo.nextInt()); IntStream.range(0, reverse).forEach(e -> bar.nextInt()); int res = foo.nextInt(round); if (res == bar.nextInt(round)) { System.out.println(res); } } }
18482 18482 2021 2021 16307
满足这种规则的数字确实稀少,除了2021还有
18482
,16307
这两个数字,但2021恰好夹在中间好尴尬,不过这个16307
为什么没有重复两次呢?我的规则是他的循环的次数,倒置过来也必须得到他呀,不应该有两次么,得看看他的循环次数,将打印行改为System.out.println(res + ":" + i + ":" + reverse);
得到
18482:17092:29071 18482:29071:17092 2021:42174:47124 2021:47124:42174 16307:53609:90635
原来是因为将他的数字倒转,就超过了我设置的循环上限0xffff,他只能打印循环53609次的16307,无法进行90635的第二次打印,我加上
reverse > n
的判断排除它即可,现在就只需要倒排序,将2021放在首位,即可得到2021了
最终代码如下:
final int seed = 0xffff;
final int round = seed >> 1;
for (int i = seed; i > 0; i--) {
Random foo = new Random(seed);
Random bar = new Random(seed);
int reverse = 0;
int temp = i;
while(temp != 0) {
reverse = reverse * 10 + (temp % 10);
temp /= 10;
}
if (i == reverse || i % 10 == 0 || reverse > seed) {
continue;
}
IntStream.range(0, i).forEach(e -> foo.nextInt());
IntStream.range(0, reverse).forEach(e -> bar.nextInt());
int res = foo.nextInt(round);
if (res == bar.nextInt(round)) {
System.out.println(res);
break;
}
}
我同样贴在了 https://gist.github.com/lyrieek/61b435d26c10aba82b17d65f180199ab