用 Java 生成数百万个随机字符串

4

我希望能够随机生成400万到5000万之间的数百万个密码。问题在于处理器需要处理这些密码所需的时间。

我想知道是否有解决方案可以在几秒钟内(最多1分钟)生成大量密码。

目前我已经实现了此功能,但需要超过3分钟的时间(即使是在很好的配置下,我也希望能在小配置上运行它)。

private final static String policy = "azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQSDFGHJKLMWXCVBN1234567890";
    private static List<String> names = new ArrayList<String>();
    
    
    public static void main(String[] args) {
        names.add("de");
        init();
    }
    
    
    
    private static String generator(){
        String password="";
        int randomWithMathRandom = (int) ((Math.random() * ( - 6)) + 6);
        for(var i=0;i<8;i++){
            randomWithMathRandom = (int) ((Math.random() * ( - 6)) + 6);
            password+= policy.charAt(randomWithMathRandom);
        }
        return password;
    }
    
    public static void init() {
        for (int i = 0; i < 40000000; i++) {
            names.add(generator());     
        }
    }

顺便说一下,我不能使用现成的列表。我认为最“昂贵”的时间浪费是输入到列表中。

我的当前配置: Ryzen 7 4800H RTX 2600 SSD NVME RAM 3200MHZ

更新: 我尝试了2000万个数据,并显示出一个错误:java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"


1
只需选择几百万个UUID即可。 - luk2302
对于我来说,处理 4000 万条数据只需要 18 秒钟,你能否编辑你的帖子并添加你的笔记本电脑配置信息? - azro
确实,对我来说也不需要花太多时间,不过试试用 StringBuilder,而不是在循环中连接字符串。 - Yassin Hajaj
我刚刚进行了一个小测试。请确保您没有被内存限制所阻塞。我的系统有5 GB的可用RAM,足以在我的机器开始交换和性能下降之前,在内存中存储约20_000_000个32字节密码。我能够在19秒内生成10_000_000个128字节密码。 - Turing85
唯一的问题是我需要在任何电脑上运行它,以免触及堆限制... :'( - Forcela8
显示剩余10条评论
2个回答

4

将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无法清除(即其内部存储结构不能归零)。可以通过多线程运行密码生成来进一步提高性能,但我将把这留给读者自己去尝试。
要使用问题中提供的字母表,我们可以删除静态字段MINMAX,定义一个新的静态字段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];

// byte-length of the first n passwords, including line breaks:
final int offset = (passwordLength + System.lineSeparator().toCharArray().length) * n;

raf.seek(offset); // skip the first n passwords
raf.read(bytes);

// reset to the beginning of the file, in case we want to read more passwords later:
raf.seek(0); 

System.out.println(new String(bytes));

非常好。如果你能解释一下:index < (chunk + 1) * chunkSize && index < numberOfPasswordsToGenerate; - Khanna111
@Forcela8 只需将您的字母表整合进去 :) 不应该太难。 - Turing85
@Turing85 这就是我提出问题的原因。min和max代表ASCII码吗?所以,如果我需要实现我的字母表,我需要在ASCII表中搜索,我猜。 - Forcela8
@Forcela8,我在我的答案底部添加了使用你的字母表所需的更改。在我的测试中,这对性能没有影响。 - Turing85
是的,这个解决方案需要约5.5秒来读取最后一个密码(最坏情况)。因此,我预计平均每次密码读取将消耗2.75秒。然而,我相信我们可以做得更好,因为所有行的长度都相等。 - Turing85
显示剩余10条评论

0

我可以给你一些优化代码并使其更快的技巧,你可以将它们与其他技巧一起使用。

  1. 如果你知道需要多少个密码,你应该创建一个字符串数组,并用循环中的变量填充它。
  2. 如果你必须使用动态大小的数据结构,请使用链表。 当添加元素是主要目标时,链表比数组列表更好,而如果你想访问它们而不是添加它们,则更糟糕。
  3. 使用 StringBuilder 而不是 += 操作符来操作字符串。 += 操作符在时间复杂度上非常“昂贵”,因为它总是创建新的字符串。使用 StringBuilder 的 append 方法可以加速你的代码。
  4. 不要使用 Math.random() 并将结果乘以你的范围数字,而是创建一个静态 Random 对象,并使用 yourRandomInstance.next(int range)。
  5. 考虑使用 ASCII 表来获取随机字符,而不是使用 str.charAt(int index) 方法,这也可能加速你的代码,建议你检查一下。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接