为何使用随机字符串的代码会打印出“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); 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. 这种方法是否存在安全问题? 由于使用固定种子生成的随机数是可预测的,因此在需要真正随机性的安全场景中,如加密,不建议使用这种方法。