Uniq发展论文

你好,我是歪的。

最近在stackoverflow上看到一段代码。

只是第一眼看上去很傻,明白了就直接跪下了!

让我先给你看看stackoverflow上的这个问题是什么,然后引出这段代码:

问题特别简单,就一句话:

谁能给我解释一下:为什么这段代码使用随机字符串打印出hello world?

代码也很简单。让我把它拿出来给你看看:

您也可以将上面的代码直接粘贴到您的运行环境中,并运行它,看看它是否也输出为hello world:

我就问你一句:就算我把所有代码都给你,我第一次看《hello world》的时候你是不是不知所措?

高赞答道。

高赞的回答也很简单,就两句话。

让我给你翻译一下。这家伙说:

当我们调用Random的构造函数时,我们会得到一个“种子”参数。比如本例中的:-229985452或者-147909649。

那么Random将从指定的种子值开始生成随机数。

并且用相同的种子构造的每个随机对象将根据相同的模式生成数字。

我没有看得很清楚,是吗?

没关系,我给你一段代码,你就能恍然大悟上面那段说的是什么:

这段代码在我的机器上运行,结果如下:

你拿着它跑,你跑的结果一定是这样的。

这是为什么呢?

答案写在Javadoc上:

在上面的代码中,两个-229985452是同一个种子,三个对nextInt()的调用是同一个调用序列。

因此,它们生成并返回看似随机的相同数字。

我们在程序中的正常用法应该是这样的:

当new Random()时,不指定值。

我们都知道Random是一种伪随机算法,在构造的时候指定种子参数是一种更伪随机的算法。

因为如果我能猜出你的种子,或者你的种子泄露了,那么理论上我就能猜出你的随机数生成序列。

我已经在前面的代码中演示了这一点。

前面稍微解释了一下“种子”的关键点,再回到品尝的问题上,大概就能看出一些端倪了。

主要看这个循环里的代码。

首先,nextInt(27)定义了当前返回的数字k必须是[0,27]之间的数字。

如果返回0,循环结束;否则,循环结束。然后做一个类型转换。

然后是char类型的强制转换。

当您看到数字变为char类型时,您应该以一种有条件的方式来考虑ascii代码:

从ascii代码表中,我们可以看到“96”是这里的符号:

因此,以下代码的范围是[96+1,96+26]:

也就是[97,122],也就是ascii码对应的a-z。

所以,我带你去反汇编上面的演示代码。

首先是NewRandom的前五次返回(-229985452)。Nextint (27)如下:

NewRandom的前五次返回(-147909649)。Nextint (27)如下:

所以,当你查看ascii码表时,你可以看到它对应的字母:

现在,至于这个高深莫测的代码为什么输出“hello world”,我心里是不是很清楚,它就像一面镜子?

看穿了,这只是一个骗局。

然后这个问题下面有个评论,给我看了另一种打开方式:

您可以指定打印hello world,因此理论上我也可以指定键入其他单词。

比如这位兄弟打了一个短语:敏捷的褐皮狐狸跳过一只懒狗。

如果直译的话,意思是“敏捷的棕狐狸和懒惰的狗杂交”,似乎是一派胡言。

但是,你要知道,我的英语水平比较高,在这里一眼看到这句话肯定不容易。

所以我查了一下:

果然有点故事,属于戏法中的戏法。

看了这位兄弟的快速褐狐例子,我有了新的想法。

既然它能打出所有的字母,我也能打出我想要的特定短语吗?

我很好,谢谢你,还有你。

在这个问题的回答中,“好心人”已经写出了查找指定单词对应的seed的功能的代码。

我直接贴,你也可以直接拿着用:

所以我在找之前提到的那句话,很简单:

而且我在跑步的时候,明显感觉自己花了很多时间在搜索“感谢”这个词。

为什么?

我给你讲个故事吧。只有一句话,你一定听过:

我们这里的generateSeed方法相当于这只猴子。感谢这个词就是莎士比亚。

在generateSeed法中,通过26个字母的连续排列组合,总是可以排列出“谢谢”,但只能排列很短的时间。

单词越长,用的时间越长。

比如我会有一句恭喜你,这么长的一句话,从00: 05到现在23个小时没跑出来:

但理论上,只要有足够的时间,这颗种子是会被找到的。

至此,你应该完全明白为什么上面提到的代码会在随机字符串的hello world中打印出来了。

你以为我想带你去看源代码?

不,我主要带你吃瓜。

首先,看看随机无参数构造函数:

好家伙,原来是一个“无参数”的shell,其实是我自己做了一个种子,然后调用了参数构造方法。

只是在构建的时候加入了变量“System.nanoTime()”,让种子看起来有点随机。

等等,前面不是还有一个“seedUniquifier”方法吗?

这个方法是这样的:

好家伙,第一次看的时候,头都大了。这里面有两个“神奇的数字”:

这个东西你也不懂?

永远不要下定决心,斯达克弗洛

一搜索就会找到这个地方:

在这个问题中,他说他也对这两个数字感到不解,他在网上搜了一圈,相关的资料很少。但是我找到一篇论文,里面提到了一个非常接近的“幻数”:

文中提到的数字如下:

看到了吗?

这个Java源代码中的数字少了一个“1”。发生了什么事?抄的时候不会出错吧?

以下是高度赞扬的回答:

“这看起来确实像是一个错误。”

很有意思。如果你要说这是我哥写Java源代码的时候抄的代码,我会激动的。

马上去Java Bug的页面用那串数字搜索,真的是出乎意料:

在对这个bug的描述中,他把我的注意力吸引到了源代码的这个地方:

原来这个地方的标注代表的是一篇论文,所以这个数字的来源一定藏在这篇论文里。

等等,为什么我觉得这篇论文的名字有点耳熟?

前一个stackoverflow中提到的链接是一个纸地址:

看看这篇论文的名字是不是和Java里的注释一样:

那肯定是一样的东西,只是一个小写和一个大写。

所以,这里是真锤。真的是第一次开始用Java写哥哥的副本数,输了一个“1”。

而且,我甚至可以想象,那位兄弟在写这部分源代码的时候,把数字“1178349727652981”贴上去,发现:咦,前面怎么有两个1?它被复制和删除了。

至于删除这个“1”,会带来什么问题?

反正这里有个关联问题,说并发调用new Random()的随机性不够大。

我没有去研究这个。有兴趣可以去看看。我只负责带你吃瓜。

于是,基于这个“瓜”,官方对这段代码进行了一次修改:

我这里正好有JDK 15和JDK 8版本的代码。我一看,真的是“1”的差别:

而关于随机数,现在很少用Random。

直接上ThreadLocalRandom,香不香?

什么,你拒绝了?