你好,我是伟伟。
我最近在stackoverflow 上看到了一些代码。我应该说什么?
第一次看到的时候我很困惑,但是当我明白的时候,我跪下了!
首先,我将向您展示stackoverflow 上的这个问题是关于什么的,然后我将引导您查看这段代码。
https://stackoverflow.com/questions/15182496/为什么-这样做-这个代码-使用-随机-字符串-打印-hello-world
问题很简单,就一句话。
有人可以解释一下吗?为什么这段代码使用随机字符串打印hello world?
代码也很简单,我们就把它拿出来运行一下。
public class MainTest { public static void main(String[] args) { System.out.println(randomString(-229985452) + ' ' + randomString(-147909649)); } public static String randomString(int i) { random ran=new Random(i); StringBuilder sb=new StringBuilder(); while (true) { int k=ran.nextInt(27); if (k==0) Break; sb.append((char) ('`' + k)); } return sb.toString(); }}您也可以将上面的代码直接粘贴到您的执行环境中并运行它,看看是否也打印了hello world。
让我问你一个问题。即使给了您一个代码,您第一次看到Hello World 时是否会感到困惑?
喜欢的答案
高赞的回答也很简单,就两句话。
请让我翻译一下。这位朋友说:
当您调用Random 的构造函数时,它会被赋予一个“种子”参数。例如,本例中为-229985452 或-147909649。
然后随机生成从指定种子值开始的随机数。
所有使用相同种子构造的Random 对象都会根据相同的模式生成数字。
你没看清楚吧?
没关系。我将向您展示一些代码。然后你就会突然明白上面这段话在说什么了。
public static void main(String[] args) {randomString(-229985452); System.out.println('------------');randomString(-229985452);} private static void randomString (int i) { Random ran=new Random(i); System.out.println(ran.nextInt()); System.out.println(ran.nextInt()); System.out.println(ran.nextInt() )); System.out.println(ran.nextInt()); System.out.println(ran.nextInt());} 这是在我的机器上运行此代码的结果:
如果你选择它并运行它,结果肯定会像这样。
为什么这个?
答案在Javadoc 中。
如果使用相同的种子创建两个Random 实例,并对每个实例进行相同的方法调用序列,则将生成并返回相同的数字序列。
在上面的代码中,两个-229985452是相同的种子,三个nextInt()调用是相同的调用序列。
因此,它生成并返回相同的、看似随机的数字。
程序中的典型用法是:
使用new Random() 时,未指定任何值。
我们都知道Random是一种伪随机算法,任何在构造过程中指定种子参数的算法都更像是伪随机算法。
因为如果我能猜到你的种子,或者如果你的种子被泄露,我理论上就能猜到随机数生成序列。
我们在前面的代码中演示了这一点。
我们再来看这个问题,简单地解释了“种子”的要点之后,我们回到品尝一品的问题,我想我们会得到一些提示。
主要看这个循环里面的代码。
首先,nextInt(27) 检查当前返回的数字k 必须是[0,27) 之间的数字。
如果返回0,则如果不为零则循环退出。接下来,进行类型转换。
下一步是将其转换为char 类型。
当您看到转换为char 类型的数字时,您会自动想到ASCII 代码。
从ASCII码表中我们可以看出“96”就是这里的符号。
因此,下面的代码的范围是[96+1, 96+26]。
''+k
它是[97,122],对应于ASCII码a到z。
现在我们再次反汇编上面的演示代码。
首先new Random(-229985452).nextInt(27)的前五个返回值为:
new Random(-147909649).nextInt(27)的前五个返回值为:
因此,如果查看ASCII码表,就可以看到相应的字符。
8 + 96=104 -- h5 + 96=101 -- e12 + 96=108 -- l12 + 96=108 -- l15 + 96=111 -- o23 + 96=119 -- w15 + 96=111 -- o18 + 96=114 -- r12 + 96=108 -- l4 + 96=100 -- d
现在,为什么这个神秘的代码会输出“hello world”呢?这让我感觉自己站在镜子里,就像云已经散去,我可以看到蓝天。
一旦你明白了,这只是一个小技巧。
然后这个问题下面有一条评论显示了另一种打开方式。
如果您可以指定hello world 的输出,理论上您也可以指定其他单词的输出。
例如,这个人输入了短语“一只敏捷的棕色狐狸跳过一只懒狗”。
直译是“敏捷的棕色狐狸跳过了懒狗”,但这似乎没有任何意义。
但你也知道,我的英语水平比较高,所以我一眼就看出这句话远不简单。
所以我检查了以下内容:
果然是有一点故事,属于诡计中的诡计。
在学习沙雕技术的同时,我还提高了英语水平,所以我能够一石二鸟。文章末尾可以加“赞”或“查看”吗?
读完这个人简单的棕色狐狸的例子后,我有了一些新的想法。
是否可以输入所有字符,甚至我需要的特定短语?
我很好,谢谢你,你等等。
诸如查找与给定单词相对应的种子等功能的代码已经由“好人”在该问题的答案中编写。
只需将其粘贴到此处,但您也可以按原样使用它。
public static longgenerateSeed(String goal, long start, long finish) {char[]input=goal.toCharArray();char[]pool=newchar[input.length];label:for(long seeds=start;seed finish;seed++) {随机Random=new Random(seed); for (int i=0; i input.length; i++) pool[i]=(char) (random.nextInt(27) + '''); if (random .nextInt( 27)==0) { for (int i=0; i input.length; i++) { if (input[i] !=pool[i]) continue label; } return Seed; } } throw new NoSuchElementException ('对不起:/');} 那么如果你想找到上面的短语,就很容易了。
当我跑步时,我明显感觉到我花了一段时间才找到合适的词来表达“谢谢”。
为什么?
请让我谈谈。只有一句话。你可能听说过它。
只要时间足够长,猴子就能敲出:010到30000之间的数字。
这里的generateSeed方法就对应了这只猴子。谢谢这个词是《莎士比亚》。
使用generateSeed方法,通过连续组合26个字符,您总是可以及时串出“谢谢”。
单词越长,花费的时间就越长。
例如,“恭喜你,这么长的单词从00:05 已经跑了23 个小时了,我还是看不懂。”
但理论上来说,只要你有足够的时间,你一定会找到这个物种。
至此,你应该完全明白为什么上面的代码使用随机字符串打印hello world 了。
源码,可以看源码吗?
不是,我主要是带她出去吃瓜。
首先,我们来看看Random 无参数构造函数。
事实上,我自己创建了种子并调用了参数化构造函数。
只是变量“System.nanoTime()”是在构建时添加的,因此种子看起来有点随机。
等等,之前不是有一个“seedUniquiifier”方法吗?
就是这样:
我第一眼看到的时候就头晕了。其中涉及两个“神奇数字”。
181783497276652981L8682522807148012L
你不也明白这一点吗?
待定,堆栈溢出。
只需搜索,您就会找到这个地方。
https://stackoverflow.com/questions/18092160/whats-with-181783497276652981-and-8682522807148012-in-random-java-7
对此,他表示自己也对这两个数字感到困惑,并在网上进行了搜索,但几乎没有找到相关信息。然而,我发现一篇论文提到了一个非常接近的“神奇数字”。
论文中列出的数字是:
你可以看到我吗?
这段Java源代码中的数字开头少了一个“1”,这到底是什么?是不是我复制的时候弄错了?
以下是评价较高的答案:
“看来是误抄了。”
这有点有趣,当我告诉他这是编写Java 源代码的人时,他很兴奋,因为他复制代码时双手在颤抖。
如果你立即进入Java Bug 页面并搜索一串数字,你会发现一些意想不到的东西。
https://bugs.openjdk.java.net/browse/JDK-8201634
在描述这个错误时,他提请我注意源代码中的以下位置:
原来,这里的注释代表的是一篇论文,而这个数字的来源应该隐藏在这篇论文中。
等等,为什么这篇论文的名字听起来很耳熟?
点击上述stackoverflow上的链接即可获取论文地址。
检查一下这篇论文的名字和关于Java的评论是否相同。
一定是一样的。只有一个小写字母和一个大写字母。
这是真实的故事。第一个编写Java源代码的人在抄写数字时手抖,抄下来少了一个“1”。
当我写这部分源代码时,有位老大爷贴上了数字“1181783497276652981”,然后说:“哎呀,前面怎么有两个1?这是重复的,删掉吧。我什至可以想象他意识到,“拜托。”
如果删除这个“1”会出现什么样的问题呢?
无论如何,问题就在这里。这意味着对new Random() 的并发调用不够随机。
我不会做任何研究,所以如果你有兴趣,请去做,我唯一的责任就是带你出去吃瓜。
于是,在这个“瓜”的基础上,官方又改了一次代码。
我这里正好有JDK 15和JDK 8版本的代码,于是我看了一下,发现居然有“1”:的区别
当谈到随机数时,现在已经很少使用Random 了。
只需使用ThreadLocalRandom。是不是很美妙?
什么,你说不?