为何使用随机字符串的代码会打印出“hello world”?

为何使用随机字符串的代码会打印出“hello world”?

技术背景

在 Java 中,java.util.Random 类用于生成伪随机数。当使用特定的种子参数初始化 Random 实例时,它会从该种子值开始遵循随机数生成算法。这意味着,使用相同种子创建的每个 Random 实例每次都会生成相同的数字模式。基于此特性,我们可以利用它来生成特定的字符串,比如“hello world”。

实现步骤

1. 确定种子值

为了生成“hello”和“world”,需要找到合适的种子值。例如,使用 Random r = new Random(-229985452) 生成“hello”,使用 Random r = new Random(-147909649) 生成“world”。

2. 生成随机数

通过 r.nextInt(27) 方法生成随机数,这些随机数对应着字母表中的字母(加上终止符,共 27 个字符)。

3. 转换为字符

将生成的随机数加上字符 ` (其 ASCII 码为 96),得到对应的字符。

核心代码

生成特定字符串的种子查找代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.lang.*;
import java.util.*;
import java.io.*;

public class RandomWords {
public static void main (String[] args) {
Set<String> wordSet = new HashSet<String>();
String fileName = (args.length > 0 ? args[0] : "/usr/share/dict/words");
readWordMap(wordSet, fileName);
System.err.println(wordSet.size() + " words read.");
findRandomWords(wordSet);
}

private static void readWordMap (Set<String> wordSet, String fileName) {
try {
BufferedReader reader = new BufferedReader(new FileReader(fileName));
String line;
while ((line = reader.readLine()) != null) {
line = line.trim().toLowerCase();
if (isLowerAlpha(line)) wordSet.add(line);
}
}
catch (IOException e) {
System.err.println("Error reading from " + fileName + ": " + e);
}
}

private static boolean isLowerAlpha (String word) {
char[] c = word.toCharArray();
for (int i = 0; i < c.length; i++) {
if (c[i] < 'a' || c[i] > 'z') return false;
}
return true;
}

private static void findRandomWords (Set<String> wordSet) {
char[] c = new char[256];
Random r = new Random();
for (long seed0 = 0; seed0 >= 0; seed0++) {
for (int sign = -1; sign <= 1; sign += 2) {
long seed = seed0 * sign;
r.setSeed(seed);
int i;
for (i = 0; i < c.length; i++) {
int n = r.nextInt(27);
if (n == 0) break;
c[i] = (char)((int)'a' + n - 1);
}
String s = new String(c, 0, i);
if (wordSet.contains(s)) {
System.out.println(s + ": " + seed);
wordSet.remove(s);
}
}
}
}
}

验证种子生成字符串的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.lang.*;
import java.util.*;

public class RandomWordsTest {
public static void main (String[] args) {
long[] a = {-73, -157512326, -112386651, 71425, -104434815,
-128911, -88019, -7691161, 1115727};
for (int i = 0; i < a.length; i++) {
Random r = new Random(a[i]);
StringBuilder sb = new StringBuilder();
int n;
while ((n = r.nextInt(27)) > 0) sb.append((char)('`' + n));
System.out.println(sb);
}
}
}

最佳实践

多线程搜索种子

为了提高搜索种子的效率,可以使用多线程。以下是一个多线程搜索种子的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class SeedFinder {

static class SearchTask implements Callable<Long> {

private final char[] goal;
private final long start, step;

public SearchTask(final String goal, final long offset, final long step) {
final char[] goalAsArray = goal.toCharArray();
this.goal = new char[goalAsArray.length + 1];
System.arraycopy(goalAsArray, 0, this.goal, 0, goalAsArray.length);
this.start = Long.MIN_VALUE + offset;
this.step = step;
}

@Override
public Long call() throws Exception {
final long LIMIT = Long.MAX_VALUE - this.step;
final Random random = new Random();
int position, rnd;
long seed = this.start;

while ((Thread.interrupted() == false) && (seed < LIMIT)) {
random.setSeed(seed);
position = 0;
rnd = random.nextInt(27);
while (((rnd == 0) && (this.goal[position] == 0))
|| ((char) ('`' + rnd) == this.goal[position])) {
++position;
if (position == this.goal.length) {
return seed;
}
rnd = random.nextInt(27);
}
seed += this.step;
}

throw new Exception("No match found");
}
}

public static void main(String[] args) {
final String GOAL = "hello".toLowerCase();
final int NUM_CORES = Runtime.getRuntime().availableProcessors();

final ArrayList<SearchTask> tasks = new ArrayList<>(NUM_CORES);
for (int i = 0; i < NUM_CORES; ++i) {
tasks.add(new SearchTask(GOAL, i, NUM_CORES));
}

final ExecutorService executor = Executors.newFixedThreadPool(NUM_CORES, new ThreadFactory() {

@Override
public Thread newThread(Runnable r) {
final Thread result = new Thread(r);
result.setPriority(Thread.MIN_PRIORITY); // make sure we do not block more important tasks
result.setDaemon(false);
return result;
}
});
try {
final Long result = executor.invokeAny(tasks);
System.out.println("Seed for \"" + GOAL + "\" found: " + result);
} catch (Exception ex) {
System.err.println("Calculation failed: " + ex);
} finally {
executor.shutdownNow();
}
}
}

常见问题

1. 为什么每次运行程序使用相同种子会得到相同结果?

这是因为 Random 类的设计特性,当使用相同的种子初始化时,它会按照相同的算法生成相同的随机数序列。

2. 对于较长的字符串,找到合适种子的概率如何?

由于伪随机数生成器(PRNG)使用 32 位种子初始化,对于长度不超过 floor[32/log₂(27)] - 1 = 5 的大多数单词,我们可以期望至少有一个种子能生成它们。对于 6 字符的单词,成功概率约为 41.06%;对于 7 字符的单词,成功概率约为 1.52%。

3. 这种方法是否存在安全问题?

由于使用固定种子生成的随机数是可预测的,因此在需要真正随机性的安全场景中,如加密,不建议使用这种方法。


为何使用随机字符串的代码会打印出“hello world”?
https://119291.xyz/posts/why-code-using-random-strings-prints-hello-world/
作者
ww
发布于
2025年5月26日
许可协议