将5千万个密码作为String
存储在内存中可能会导致问题,因为堆栈或堆可能会溢出。 从这个角度来看,我认为我们能做的最好的事情是生成一大块密码,将其存储在文件中,生成下一块,将它们附加到文件中,直到创建所需数量的密码为止。 我编写了一个小程序,可以生成长度为32
的随机String
。 作为字母表,我使用了所有ASCII字符,介于'!'
(ASCII值33
)和'~'
(ASCII值126
)之间。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Random;
import java.util.concurrent.TimeUnit;
class Scratch {
private static final int MIN = '!';
private static final int MAX = '~';
private static final Random RANDOM = new Random();
public static void main(final String... args) throws IOException {
final Path passwordFile = Path.of("passwords.txt");
if (!Files.exists(passwordFile)) {
Files.createFile(passwordFile);
}
final DecimalFormat df = new DecimalFormat();
final DecimalFormatSymbols ds = df.getDecimalFormatSymbols();
ds.setGroupingSeparator('_');
df.setDecimalFormatSymbols(ds);
final int numberOfPasswordsToGenerate = 50_000_000;
final int chunkSize = 1_000_000;
final int passwordLength = 32;
int generated = 0;
int chunk = 0;
final long start = System.nanoTime();
while (generated < numberOfPasswordsToGenerate) {
final StringBuilder passwords = new StringBuilder();
for (
int index = chunk * chunkSize;
index < (chunk + 1) * chunkSize && index < numberOfPasswordsToGenerate;
++index) {
final StringBuilder password = new StringBuilder();
for (int character = 0; character < passwordLength; ++character) {
password.append(fetchRandomLetterFromAlphabet());
}
passwords.append(password.toString()).append(System.lineSeparator());
++generated;
if (generated % 500_000 == 0) {
System.out.printf(
"%s / %s%n",
df.format(generated),
df.format(numberOfPasswordsToGenerate));
}
}
++chunk;
Files.writeString(passwordFile, passwords.toString(), StandardOpenOption.APPEND);
}
final long consumed = System.nanoTime() - start;
System.out.printf("Done. Took %d seconds%n", TimeUnit.NANOSECONDS.toSeconds(consumed));
}
private static char fetchRandomLetterFromAlphabet() {
return (char) (RANDOM.nextInt(MAX - MIN + 1) + MIN);
}
}
在我的笔记本电脑上,该程序的结果良好。它只需要约33秒便能完成,所有的密码都存储在一个单独的文件中。
该程序是一个概念验证,并非生产就绪。例如,如果已存在一个名为
password.txt
的文件,则会将内容追加到该文件中。对于我来说,仅运行一次后该文件已经有1.7 GB大小,所以请注意此点。此外,生成的密码被临时存储在
StringBuilder
中,这可能存在安全风险,因为
StringBuilder
无法清除(即其内部存储结构不能归零)。可以通过多线程运行密码生成来进一步提高性能,但我将把这留给读者自己去尝试。
要使用问题中提供的字母表,我们可以删除静态字段
MIN
和
MAX
,定义一个新的静态字段
private static final char[] ALPHABET = "azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN1234567890".toCharArray();
并重新实现
fetchRandomLetterFromAlphabet
方法:
private static char fetchRandomLetterFromAlphabet() {
return ALPHABET[RANDOM.nextInt(ALPHABET.length)];
}
我们可以使用以下代码片段在常量时间内读取文件中第n个(从0开始)密码的值:
final int n = ...;
final RandomAccessFile raf = new RandomAccessFile(passwordFile.toString(), "r");
final long start = System.nanoTime();
final byte[] bytes = new byte[passwordLength];
final int offset = (passwordLength + System.lineSeparator().toCharArray().length) * n;
raf.seek(offset);
raf.read(bytes);
raf.seek(0);
System.out.println(new String(bytes));
20_000_000
个32字节密码。我能够在19秒内生成10_000_000
个128字节密码。 - Turing85