如何生成随机的字母数字字符串

1968

我一直在寻找一个简单的Java算法来生成伪随机的字母数字字符串。在我的情况下,它将被用作唯一的会话/键标识符,可能在500K+次生成中是唯一的(我的需求并不需要更复杂的东西)。

理想情况下,我希望能够根据我的唯一性需求指定长度。例如,长度为12的生成字符串可能看起来像"AEYGF7K0DM1X"


165
注意生日悖论。 - pablosaraiva
63
即使考虑到生日悖论,如果您使用12个字母数字字符(共62个字符),仍需要超过340亿个字符串才能达到悖论。而且,生日悖论并不保证一定会发生冲突,它只是表示有超过50%的概率。 - NullUserException
6
@NullUserException:每次尝试的成功率达到50%真是太高了,即使尝试10次,成功率也达到0.999。考虑到你可以在24小时内尝试很多次,因此你不需要340亿个字符串就足以确信至少猜中其中一个。这就是为什么某些会话令牌应该非常非常长的原因。 - Pijusn
20
我认为这3个单行代码非常有用。Long.toHexString(Double.doubleToLongBits(Math.random())); 生成一个16进制的随机字符串。UUID.randomUUID().toString(); 生成一个随机唯一标识符。RandomStringUtils.randomAlphanumeric(12); 生成一个包含12个字符的随机字母数字组合。 - Manindar
25
我知道这已经是老话题了,但是,在生日悖论中,“50%的机率”不是“每一次尝试”,而是“存在至少一对重复的机率为50%,在(这种情况下)340亿个字符串中”。你需要拥有1.6*10^21-1.6e21个条目,才能每次尝试有50%的机会。 - Tin Wizard
显示剩余3条评论
46个回答

32

令人惊讶的是,这里没有人提过,但是:

import java.util.UUID

UUID.randomUUID().toString();

很容易。

这样做的好处是UUID既漂亮又长,并且几乎不可能发生碰撞。维基百科有一个很好的解释

"...每秒生成10亿个UUID,在接下来的100年中,仅创建一个重复项的概率约为50%。"

前4位是版本类型,后2位是变体类型,因此您可以获得122位随机数。所以如果您想要,可以从末尾截断以减小UUID的大小。虽然不建议这样做,但您仍然有足够的随机性,足以处理500k条记录。


49
有人在你之前大约一年建议过这个。 - erickson

18

Java 8 中的替代方法是:

static final Random random = new Random(); // Or SecureRandom
static final int startChar = (int) '!';
static final int endChar = (int) '~';

static String randomString(final int maxLength) {
  final int length = random.nextInt(maxLength + 1);
  return random.ints(length, startChar, endChar + 1)
        .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
        .toString();
}

3
很好,但如果你想严格限制只使用数字和字母(0-9, a-z, A-Z),请查看这里:http://www.rationaljava.com/2015/06/java8-generate-random-string-in-one-line.html。 - Dan

12
public static String generateSessionKey(int length){
    String alphabet =
        new String("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); // 9

    int n = alphabet.length(); // 10

    String result = new String();
    Random r = new Random(); // 11

    for (int i=0; i<length; i++) // 12
        result = result + alphabet.charAt(r.nextInt(n)); //13

    return result;
}

需要解释一下。 - Peter Mortensen

11
import java.util.Random;

public class passGen{
    // Version 1.0
    private static final String dCase = "abcdefghijklmnopqrstuvwxyz";
    private static final String uCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String sChar = "!@#$%^&*";
    private static final String intChar = "0123456789";
    private static Random r = new Random();
    private static StringBuilder pass = new StringBuilder();

    public static void main (String[] args) {
        System.out.println ("Generating pass...");
        while (pass.length () != 16){
            int rPick = r.nextInt(4);
            if (rPick == 0){
                int spot = r.nextInt(26);
                pass.append(dCase.charAt(spot));
            } else if (rPick == 1) {
                int spot = r.nextInt(26);
                pass.append(uCase.charAt(spot));
            } else if (rPick == 2) {
                int spot = r.nextInt(8);
                pass.append(sChar.charAt(spot));
            } else {
                int spot = r.nextInt(10);
                pass.append(intChar.charAt(spot));
            }
        }
        System.out.println ("Generated Pass: " + pass.toString());
    }
}

我只是将密码添加到字符串中...没错,它运行良好。试一下吧...非常简单;我写的。


1
我对代码进行了一些微小的修改。你为什么经常添加“+0”?你为什么要分开声明和初始化?使用1、2、3、4代替0、1、2、3有什么优势?最重要的是:你取了一个随机值,并且与if-else语句比较了四次一个新值,这可能会导致不匹配,并没有增加更多的随机性。但如果需要,请随意回滚。 - user unknown

11

使用UUID是不安全的,因为UUID的一部分根本不是随机的。 erickson的过程非常整洁,但它不会创建相同长度的字符串。下面的代码片段应该足够了:

/*
 * The random generator used by this class to create random keys.
 * In a holder class to defer initialization until needed.
 */
private static class RandomHolder {
    static final Random random = new SecureRandom();
    public static String randomKey(int length) {
        return String.format("%"+length+"s", new BigInteger(length*5/*base 32,2^5*/, random)
            .toString(32)).replace('\u0020', '0');
    }
}

为什么选择length*5?我们假设一个长度为1的随机字符串,即一个随机字符。要获得包含所有数字0-9和字符a-z的随机字符,我们需要一个随机数,在0到35之间,以获得每个字符中的一个。

BigInteger提供了一个构造函数,用于生成一个随机数,均匀分布在范围0到(2^numBits - 1)内。不幸的是,35不是可以接收到的2^numBits - 1的数字。

因此,我们有两个选择:要么选择2^5-1=31,要么选择2^6-1=63。如果我们选择2^6,我们将得到许多“不必要”/“更长”的数字。因此,2^5是更好的选择,即使我们失去了四个字符(w-z)。现在,要生成特定长度的字符串,我们可以简单地使用2^(length*numBits)-1数字。最后一个问题是,如果我们想要一个特定长度的字符串,随机数可能会生成一个较小的数字,因此长度不符合要求,所以我们必须用前导零填充字符串到所需的长度。


你能更好地解释一下5吗? - Julian Suarez

9
我找到了一个解决方案,可以生成一个随机的十六进制编码字符串。提供的单元测试似乎符合我的主要使用情况。虽然它比其他提供的答案稍微复杂一些。
/**
 * Generate a random hex encoded string token of the specified length
 *  
 * @param length
 * @return random hex string
 */
public static synchronized String generateUniqueToken(Integer length){ 
    byte random[] = new byte[length];
    Random randomGenerator = new Random();
    StringBuffer buffer = new StringBuffer();

    randomGenerator.nextBytes(random);

    for (int j = 0; j < random.length; j++) {
        byte b1 = (byte) ((random[j] & 0xf0) >> 4);
        byte b2 = (byte) (random[j] & 0x0f);
        if (b1 < 10)
            buffer.append((char) ('0' + b1));
        else
            buffer.append((char) ('A' + (b1 - 10)));
        if (b2 < 10)
            buffer.append((char) ('0' + b2));
        else
            buffer.append((char) ('A' + (b2 - 10)));
    }
    return (buffer.toString());
}

@Test
public void testGenerateUniqueToken(){
    Set set = new HashSet();
    String token = null;
    int size = 16;

    /* Seems like we should be able to generate 500K tokens 
     * without a duplicate 
     */
    for (int i=0; i<500000; i++){
        token = Utility.generateUniqueToken(size);

        if (token.length() != size * 2){
            fail("Incorrect length");
        } else if (set.contains(token)) {
            fail("Duplicate token generated");
        } else{
            set.add(token);
        }
    }
}

我认为仅基于概率而因重复标记而失败是不公平的。 - Thom Wiggers

8
  1. 根据您的需求更改字符串中的字符。

  2. 字符串是不可变的。在这里,StringBuilder.append比字符串拼接更高效。

public static String getRandomString(int length) {
    final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+";
    StringBuilder result = new StringBuilder();

    while(length > 0) {
        Random rand = new Random();
        result.append(characters.charAt(rand.nextInt(characters.length())));
        length--;
    }
    return result.toString();
}

3
这并没有增加之前已经给出的答案所没覆盖到的内容。在循环的每次迭代中创建一个新的 Random 实例是低效的。 - erickson

7
import java.util.Date;
import java.util.Random;

public class RandomGenerator {

  private static Random random = new Random((new Date()).getTime());

    public static String generateRandomString(int length) {
      char[] values = {'a','b','c','d','e','f','g','h','i','j',
               'k','l','m','n','o','p','q','r','s','t',
               'u','v','w','x','y','z','0','1','2','3',
               '4','5','6','7','8','9'};

      String out = "";

      for (int i=0;i<length;i++) {
          int idx=random.nextInt(values.length);
          out += values[idx];
      }
      return out;
    }
}

7

我不太喜欢关于“简单”解决方案的任何答案:S

我会选择一个简单的;), 纯Java, 一行代码(熵基于随机字符串长度和给定字符集):

public String randomString(int length, String characterSet) {
    return IntStream.range(0, length).map(i -> new SecureRandom().nextInt(characterSet.length())).mapToObj(randomInt -> characterSet.substring(randomInt, randomInt + 1)).collect(Collectors.joining());
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); // The character set can basically be anything
    }
}

或者(稍微可读的老方法)
public String randomString(int length, String characterSet) {
    StringBuilder sb = new StringBuilder(); // Consider using StringBuffer if needed
    for (int i = 0; i < length; i++) {
        int randomInt = new SecureRandom().nextInt(characterSet.length());
        sb.append(characterSet.substring(randomInt, randomInt + 1));
    }
    return sb.toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); // The character set can basically be anything
    }
}

但另一方面,您也可以选择使用UUID,它具有相当好的熵:

UUID.randomUUID().toString().replace("-", "")

7

我正在使用来自Apache Commons的库来生成字母数字字符串:

import org.apache.commons.lang3.RandomStringUtils;

String keyLength = 20;
RandomStringUtils.randomAlphanumeric(keylength);

它快速简单!


非常棒!而且还有随机大小写,这正是我需要的。 - user2677034

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