如何在Java中生成随机字符串而不抓狂?
\n\n想象一下:你在编写网站注册功能。需要为邮箱验证生成唯一令牌。或者为新用户生成密码。又或者只是为链接生成一个可读的ID。首先想到的是使用UUID.randomUUID(),去掉连字符,然后万事大吉。
等等。你知道吗,这个看似简单的操作可能会成为性能的瓶颈?Stack Overflow上"How to generate a random alpha-numeric string"这个问题之所以获得数千次浏览,绝非偶然。魔鬼总是藏在细节中:随机数生成器的选择、算法、内存操作。在本文中,我们不仅要编写代码——还要将其优化到极致。
\n\n准备好IntelliJ IDEA。我们开始吧!
\n\n1. 最简单(但最慢)的方法:UUID
\n\n从经典方法开始。UUID生成一个128位的值。如果去掉连字符,会得到一个32字符的十六进制字符串(0-9, a-f)。这已经是字母数字字符串了,但字母表有限。
public static String generateWithUUID() {\n return UUID.randomUUID().toString().replace("-", "");\n}\n\n\n优点:一行代码。无需思考。
\n缺点:无法控制长度。仅限十六进制字符(16个字符的字母表)。用于密码——不够强。用于令牌——勉强可用,但有个问题。
\n\n顺便说一句,UUID.randomUUID()底层使用了SecureRandom。这是加密安全的,但很慢。如果你每秒需要生成10,000个字符串——这个方法会让你失望。
2. Apache Commons Lang中的RandomStringUtils
\n\n这是一个从一个项目流传到另一个项目的经典方法。commons-lang3库提供了一个现成的方法:
import org.apache.commons.lang3.RandomStringUtils;\n\npublic static String generateWithCommons(int length) {\n return RandomStringUtils.randomAlphanumeric(length);\n}\n\n\n漂亮吗?是的。性能好吗?不太好。
\n在底层,RandomStringUtils使用java.util.Random(非加密安全)并创建许多临时对象。对于大多数任务来说足够了,但如果你要生成数百万个字符串——准备好应对GC暂停。
3. 使用Random.nextInt()的自定义方法
\n\n让我们自己造个轮子。最直接的方法:
\n\nimport java.security.SecureRandom;\nimport java.util.Random;\n\npublic class RandomStringGenerator {\n private static final String ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";\n private static final int BASE = ALPHANUMERIC.length();\n private final Random random;\n\n public RandomStringGenerator() {\n this.random = new SecureRandom(); // 为了加密安全性\n }\n\n public String generate(int length) {\n StringBuilder sb = new StringBuilder(length);\n for (int i = 0; i < length; i++) {\n sb.append(ALPHANUMERIC.charAt(random.nextInt(BASE)));\n }\n return sb.toString();\n }\n}\n\n\n这里重要的是什么?
\n- \n
- 我们使用已知容量的
StringBuilder——不会有多余的扩容。 \n SecureRandom保证输出不可预测。对于令牌——这是必须的。 \n - 字母表由字符串定义——62个字符(26+26+10)。可以添加特殊字符。 \n
但有一个问题:在Linux上SecureRandom可能很慢(等待熵)。如果速度比安全性更重要——请使用ThreadLocalRandom.current()。
4. 使用char[]和ThreadLocalRandom进行优化
\n\n想要榨干性能?放弃StringBuilder,直接操作字符数组:
import java.util.concurrent.ThreadLocalR