关于C#:如何生成随机字母数字字符串?

 2019-04-22 

How can I generate random alphanumeric strings?

如何在C_中生成随机的8个字母数字字符串?


我听说Linq是新的黑色,下面是我使用Linq的尝试:

1
2
3
4
5
6
7
private static Random random = new Random();
public static string RandomString(int length)
{
    const string chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

(注意:使用Random类不适用于任何与安全相关的内容,如创建密码或令牌。如果需要强随机数生成器,请使用RNGCryptoServiceProvider类。)


1
2
3
4
5
6
7
8
9
10
var chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[8];
var random = new Random();

for (int i = 0; i < stringChars.Length; i++)
{
    stringChars[i] = chars[random.Next(chars.Length)];
}

var finalString = new String(stringChars);

没有Linq解决方案那么优雅。

(注意:使用Random类不适用于任何与安全相关的内容,如创建密码或令牌。如果需要强随机数生成器,请使用RNGCryptoServiceProvider类。)


这个实现(通过谷歌找到)在我看来是合理的。

与所提出的一些备选方案不同,这一方案是加密的。

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
using System.Security.Cryptography;
using System.Text;

namespace UniqueKey
{
    public class KeyGenerator
    {
        public static string GetUniqueKey(int size)
        {
            char[] chars =
               "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
            byte[] data = new byte[size];
            using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
            {
                crypto.GetBytes(data);
            }
            StringBuilder result = new StringBuilder(size);
            foreach (byte b in data)
            {
                result.Append(chars[b % (chars.Length)]);
            }
            return result.ToString();
        }
    }
}

从这里的备选方案讨论中选出这个


解决方案1-最大"范围"和最灵活的长度

1
2
3
4
5
6
7
8
9
string get_unique_string(int string_length) {
    using(var rng = new RNGCryptoServiceProvider()) {
        var bit_count = (string_length * 6);
        var byte_count = ((bit_count + 7) / 8); // rounded up
        var bytes = new byte[byte_count];
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes);
    }
}

这个解决方案比使用guid有更多的范围,因为guid有两个固定位,它们总是相同的,因此不是随机的,例如十六进制的13个字符总是"4"-至少在版本6 guid中是这样。

此解决方案还允许您生成任意长度的字符串。

解决方案2-一行代码-最多可容纳22个字符

1
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 8);

只要解决方案1和字符串的范围不同(由于guid中的固定位),就不能生成字符串,但在很多情况下,这会起到作用。

解决方案3-代码略少

1
Guid.NewGuid().ToString("n").Substring(0, 8);

主要是为了历史目的把它保存在这里。它使用的代码稍微少一点,但这是因为它使用十六进制而不是base64,因此与其他解决方案相比,它需要更多的字符来表示相同的范围。

这意味着碰撞的可能性更大——用100000次8个字符串的迭代测试它会生成一个副本。


下面是我从SamAllen的例子中偷的一个例子。

如果您只需要8个字符,那么在system.io命名空间中使用path.getrandomFileName()。山姆说,在这里使用"path.getrandomFileName方法有时更优越,因为它使用rngcryptoServiceProvider来实现更好的随机性。但是,它仅限于11个随机字符。"

GetRandomFileName始终返回一个12个字符的字符串,句点为第9个字符。所以您需要去掉句点(因为这不是随机的),然后从字符串中提取8个字符。实际上,您可以只取前8个字符,而不必担心句号。

1
2
3
4
5
6
public string Get8CharacterRandomString()
{
    string path = Path.GetRandomFileName();
    path = path.Replace(".",""); // Remove period.
    return path.Substring(0, 8);  // Return 8 character string
}

谢谢山姆


我的代码的主要目标是:

  • 弦的分布几乎是一致的(不关心微小的偏差,只要它们很小)
  • 它为每个参数集输出超过数十亿个字符串。如果您的prng只生成20亿(31位熵)的不同值,那么生成8个字符的字符串(大约47位熵)是没有意义的。
  • 它是安全的,因为我希望人们将它用于密码或其他安全令牌。
  • 第一个属性是通过对字母大小取64位值模来实现的。对于小字母(如问题中的62个字符),这会导致可忽略的偏差。第二和第三个属性是通过使用RNGCryptoServiceProvider而不是System.Random实现的。

    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
    using System;
    using System.Security.Cryptography;

    public static string GetRandomAlphanumericString(int length)
    {
        const string alphanumericCharacters =
           "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
           "abcdefghijklmnopqrstuvwxyz" +
           "0123456789";
        return GetRandomString(length, alphanumericCharacters);
    }

    public static string GetRandomString(int length, IEnumerable<char> characterSet)
    {
        if (length < 0)
            throw new ArgumentException("length must not be negative","length");
        if (length > int.MaxValue / 8) // 250 million chars ought to be enough for anybody
            throw new ArgumentException("length is too big","length");
        if (characterSet == null)
            throw new ArgumentNullException("characterSet");
        var characterArray = characterSet.Distinct().ToArray();
        if (characterArray.Length == 0)
            throw new ArgumentException("characterSet must not be empty","characterSet");

        var bytes = new byte[length * 8];
        var result = new char[length];
        using (var cryptoProvider = new RNGCryptoServiceProvider())
        {
            cryptoProvider.GetBytes(bytes);
        }
        for (int i = 0; i < length; i++)
        {
            ulong value = BitConverter.ToUInt64(bytes, i * 8);
            result[i] = characterArray[value % (uint)characterArray.Length];
        }
        return new string(result);
    }


    最简单的:

    1
    2
    3
    4
    public static string GetRandomAlphaNumeric()
    {
        return Path.GetRandomFileName().Replace(".","").Substring(0, 8);
    }

    如果硬编码char数组并依赖System.Random,可以获得更好的性能:

    1
    2
    3
    4
    5
    public static string GetRandomAlphaNumeric()
    {
        var chars ="abcdefghijklmnopqrstuvwxyz0123456789";
        return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
    }

    如果你担心英文字母会在某个时候改变,你可能会失去生意,那么你可以避免硬编码,但应该表现得稍差(与Path.GetRandomFileName方法相比)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static string GetRandomAlphaNumeric()
    {
        var chars = 'a'.To('z').Concat('0'.To('9')).ToList();
        return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
    }

    public static IEnumerable<char> To(this char start, char end)
    {
        if (end < start)
            throw new ArgumentOutOfRangeException("the end char should not be less than start char", innerException: null);
        return Enumerable.Range(start, end - start + 1).Select(i => (char)i);
    }

    最后两种方法如果能使它们成为System.Random实例上的扩展方法,看起来会更好。


    只需对本主题中的各种答案进行一些性能比较:

    方法和设置

    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
    // what's available
    public static string possibleChars ="abcdefghijklmnopqrstuvwxyz";
    // optimized (?) what's available
    public static char[] possibleCharsArray = possibleChars.ToCharArray();
    // optimized (precalculated) count
    public static int possibleCharsAvailable = possibleChars.Length;
    // shared randomization thingy
    public static Random random = new Random();


    // http://stackoverflow.com/a/1344242/1037948
    public string LinqIsTheNewBlack(int num) {
        return new string(
        Enumerable.Repeat(possibleCharsArray, num)
                  .Select(s => s[random.Next(s.Length)])
                  .ToArray());
    }

    // http://stackoverflow.com/a/1344258/1037948
    public string ForLoop(int num) {
        var result = new char[num];
        while(num-- > 0) {
            result[num] = possibleCharsArray[random.Next(possibleCharsAvailable)];
        }
        return new string(result);
    }

    public string ForLoopNonOptimized(int num) {
        var result = new char[num];
        while(num-- > 0) {
            result[num] = possibleChars[random.Next(possibleChars.Length)];
        }
        return new string(result);
    }

    public string Repeat(int num) {
        return new string(new char[num].Select(o => possibleCharsArray[random.Next(possibleCharsAvailable)]).ToArray());
    }

    // http://stackoverflow.com/a/1518495/1037948
    public string GenerateRandomString(int num) {
      var rBytes = new byte[num];
      random.NextBytes(rBytes);
      var rName = new char[num];
      while(num-- > 0)
        rName[num] = possibleCharsArray[rBytes[num] % possibleCharsAvailable];
      return new string(rName);
    }

    //SecureFastRandom - or SolidSwiftRandom
    static string GenerateRandomString(int Length) //Configurable output string length
    {
        byte[] rBytes = new byte[Length];
        char[] rName = new char[Length];
        SolidSwiftRandom.GetNextBytesWithMax(rBytes, biasZone);
        for (var i = 0; i < Length; i++)
        {
            rName[i] = charSet[rBytes[i] % charSet.Length];
        }
        return new string(rName);
    }

    结果

    在LinqPad中测试。对于字符串大小10,生成:

    • from Linq = chdgmevhcy [10]
    • from Loop = gtnoaryhxr [10]
    • from Select = rsndbztyby [10]
    • from GenerateRandomString = owyefjjakj [10]
    • from SecureFastRandom = VzougLYHYP [10]
    • from SecureFastRandom-NoCache = oVQXNGmO1S [10]

    性能数字往往略有不同,很偶然,NonOptimized实际上更快,有时ForLoopGenerateRandomString交换机处于领先地位。

    • LinqIsTheNewBlack (10000x) = 96762 ticks elapsed (9.6762 ms)
    • ForLoop (10000x) = 28970 ticks elapsed (2.897 ms)
    • ForLoopNonOptimized (10000x) = 33336 ticks elapsed (3.3336 ms)
    • Repeat (10000x) = 78547 ticks elapsed (7.8547 ms)
    • GenerateRandomString (10000x) = 27416 ticks elapsed (2.7416 ms)
    • SecureFastRandom (10000x) = 13176 ticks elapsed (5ms) lowest [Different machine]
    • SecureFastRandom-NoCache (10000x) = 39541 ticks elapsed (17ms) lowest [Different machine]


    有一行代码Membership.GeneratePassword()起作用。

    这是一个相同的演示。


    EricJ.编写的代码相当草率(很明显这是6年前的代码)。他今天可能不会写这些代码),甚至还有一些问题。

    Unlike some of the alternatives presented, this one is cryptographically sound.

    不真实…密码中有一个偏差(如注释中所写),bcdefgh比其他密码的概率要高一些(a不是因为GetNonZeroBytes不生成值为零的字节,所以a的偏差是由它来平衡的),所以它不是真正的密码声音。

    这样可以解决所有问题。

    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
    public static string GetUniqueKey(int size = 6, string chars ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
    {
        using (var crypto = new RNGCryptoServiceProvider())
        {
            var data = new byte[size];

            // If chars.Length isn't a power of 2 then there is a bias if
            // we simply use the modulus operator. The first characters of
            // chars will be more probable than the last ones.

            // buffer used if we encounter an unusable random byte. We will
            // regenerate it in this buffer
            byte[] smallBuffer = null;

            // Maximum random number that can be used without introducing a
            // bias
            int maxRandom = byte.MaxValue - ((byte.MaxValue + 1) % chars.Length);

            crypto.GetBytes(data);

            var result = new char[size];

            for (int i = 0; i < size; i++)
            {
                byte v = data[i];

                while (v > maxRandom)
                {
                    if (smallBuffer == null)
                    {
                        smallBuffer = new byte[1];
                    }

                    crypto.GetBytes(smallBuffer);
                    v = smallBuffer[0];
                }

                result[i] = chars[v % chars.Length];
            }

            return new string(result);
        }
    }

    另一种选择是使用LINQ并将随机字符聚合到StringBuilder中。

    1
    2
    3
    4
    5
    6
    var&nbsp;chars&nbsp;=&nbsp;"abcdefghijklmnopqrstuvwxyz123456789".ToArray();
    string pw =&nbsp;Enumerable.Range(0,&nbsp;passwordLength)
                          .Aggregate(
                              new&nbsp;StringBuilder(),
                              (sb,&nbsp;n)&nbsp;=>&nbsp;sb.Append((chars[random.Next(chars.Length)])),
                            &nbsp; sb&nbsp;=>&nbsp;sb.ToString());

    我们也使用自定义字符串随机,但我们实现的是作为字符串的助手,因此它提供了一些灵活性…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static string Random(this string chars, int length = 8)
    {
        var randomString = new StringBuilder();
        var random = new Random();

        for (int i = 0; i < length; i++)
            randomString.Append(chars[random.Next(chars.Length)]);

        return randomString.ToString();
    }

    用法

    1
    var random ="ABCDEFGHIJKLMNOPQRSTUVWXYZ".Random();

    1
    var random ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".Random(16);

    我的简单的一行代码适用于我:)

    1
    2
    3
    4
    string  random = string.Join("", Guid.NewGuid().ToString("n").Take(8).Select(o => o));

    Response.Write(random.ToUpper());
    Response.Write(random.ToLower());

    在此基础上展开任意长度的字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        public static string RandomString(int length)
        {
            //length = length < 0 ? length * -1 : length;
            var str ="";

            do
            {
                str += Guid.NewGuid().ToString().Replace("-","");
            }

            while (length > str.Length);

            return str.Substring(0, length);
        }


    问:为什么我要浪费时间使用Enumerable.Range,而不是输入"ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"

    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
    using System;
    using System.Collections.Generic;
    using System.Linq;

    public class Test
    {
        public static void Main()
        {
            var randomCharacters = GetRandomCharacters(8, true);
            Console.WriteLine(new string(randomCharacters.ToArray()));
        }

        private static List<char> getAvailableRandomCharacters(bool includeLowerCase)
        {
            var integers = Enumerable.Empty<int>();
            integers = integers.Concat(Enumerable.Range('A', 26));
            integers = integers.Concat(Enumerable.Range('0', 10));

            if ( includeLowerCase )
                integers = integers.Concat(Enumerable.Range('a', 26));

            return integers.Select(i => (char)i).ToList();
        }

        public static IEnumerable<char> GetRandomCharacters(int count, bool includeLowerCase)
        {
            var characters = getAvailableRandomCharacters(includeLowerCase);
            var random = new Random();
            var result = Enumerable.Range(0, count)
                .Select(_ => characters[random.Next(characters.Count)]);

            return result;
        }
    }

    答案:魔法弦是坏的。有没有人注意到我的绳子上面没有"EDOCX1"(9)?因为这个原因,我妈妈教我不要使用魔法弦…

    注意1:正如其他许多人如@dtb所说,如果你需要密码安全,不要使用System.Random

    注意2:这个答案不是最有效的,也不是最短的,但我希望有足够的空间把答案和问题分开。我的答案的目的更多的是警告魔术弦,而不是提供一个奇特的创新答案。


    DTB解决方案的稍清洁版本。

    1
    2
    3
    4
        var chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        var random = new Random();
        var list = Enumerable.Repeat(0, 8).Select(x=>chars[random.Next(chars.Length)]);
        return string.Join("", list);

    您的风格偏好可能会有所不同。


    1
    2
    3
    4
    5
    6
     public static string RandomString(int length)
        {
            const string chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            var random = new Random();
            return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
        }

    很可怕,我知道,但我就是忍不住:

    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
    <wyn>
    namespace ConsoleApplication2
    {
        using System;
        using System.Text.RegularExpressions;

        class Program
        {
            static void Main(string[] args)
            {
                Random adomRng = new Random();
                string rndString = string.Empty;
                char c;

                for (int i = 0; i < 8; i++)
                {
                    while (!Regex.IsMatch((c=Convert.ToChar(adomRng.Next(48,128))).ToString(),"[A-Za-z0-9]"));
                    rndString += c;
                }

                Console.WriteLine(rndString + Environment.NewLine);
            }
        }
    }
    </wyn>

    在回顾了其他答案并考虑了codeinchaos的评论后,以及codeinchaos仍然偏向(尽管较少)的答案,我认为需要最终的剪切粘贴解决方案。所以在更新我的答案时,我决定全力以赴。

    有关此代码的最新版本,请访问BitBucket上的新hg存储库:https://bitback.org/merarischroeder/secureswiftrandom。我建议您从以下位置复制和粘贴代码:https://bitback.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b213379ecdf0ffc7496ea/code/alivate.solidswiftrandom/solidswiftrandom.cs?at=default&fileviewer=file view default(请确保单击"原始"按钮以使复制更容易,并确保您有最新版本,我认为此链接指向代码的特定版本,而不是最新版本)。

    更新说明:

  • 与其他一些答案相关-如果知道输出的长度,则不需要StringBuilder,并且在使用ToCharArray时,这将创建并填充数组(不需要先创建空数组)。
  • 与其他答案相关-您应该使用nextbytes,而不是一次获得一个性能。
  • 从技术上讲,您可以固定字节数组以便更快地访问。当您在一个字节数组上迭代超过6-8次时,它通常是值得的。(这里没有做)
  • 使用RNGCryptoServiceProvider实现最佳随机性
  • 使用一个1MB的随机数据缓存-基准测试显示缓存的单字节访问速度大约快1000倍-超过1MB需要9ms,而未缓存则需要989ms。
  • 在我的新班级里优化了对偏倚区的拒绝。
  • 问题的最终解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static char[] charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
    static int byteSize = 256; //Labelling convenience
    static int biasZone = byteSize - (byteSize % charSet.Length);
    public string GenerateRandomString(int Length) //Configurable output string length
    {
        byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
        char[] rName = new char[Length];
        SecureFastRandom.GetNextBytesMax(rBytes, biasZone);
        for (var i = 0; i < Length; i++)
        {
            rName[i] = charSet[rBytes[i] % charSet.Length];
        }
        return new string(rName);
    }

    但你需要我的新(未经测试)课程:

    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
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    /// <summary>
    /// My benchmarking showed that for RNGCryptoServiceProvider:
    /// 1. There is negligable benefit of sharing RNGCryptoServiceProvider object reference
    /// 2. Initial GetBytes takes 2ms, and an initial read of 1MB takes 3ms (starting to rise, but still negligable)
    /// 2. Cached is ~1000x faster for single byte at a time - taking 9ms over 1MB vs 989ms for uncached
    /// </summary>
    class SecureFastRandom
    {
        static byte[] byteCache = new byte[1000000]; //My benchmark showed that an initial read takes 2ms, and an initial read of this size takes 3ms (starting to raise)
        static int lastPosition = 0;
        static int remaining = 0;

        /// <summary>
        /// Static direct uncached access to the RNGCryptoServiceProvider GetBytes function
        /// </summary>
        /// <param name="buffer"></param>
        public static void DirectGetBytes(byte[] buffer)
        {
            using (var r = new RNGCryptoServiceProvider())
            {
                r.GetBytes(buffer);
            }
        }

        /// <summary>
        /// Main expected method to be called by user. Underlying random data is cached from RNGCryptoServiceProvider for best performance
        /// </summary>
        /// <param name="buffer"></param>
        public static void GetBytes(byte[] buffer)
        {
            if (buffer.Length > byteCache.Length)
            {
                DirectGetBytes(buffer);
                return;
            }

            lock (byteCache)
            {
                if (buffer.Length > remaining)
                {
                    DirectGetBytes(byteCache);
                    lastPosition = 0;
                    remaining = byteCache.Length;
                }

                Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
                lastPosition += buffer.Length;
                remaining -= buffer.Length;
            }
        }

        /// <summary>
        /// Return a single byte from the cache of random data.
        /// </summary>
        /// <returns></returns>
        public static byte GetByte()
        {
            lock (byteCache)
            {
                return UnsafeGetByte();
            }
        }

        /// <summary>
        /// Shared with public GetByte and GetBytesWithMax, and not locked to reduce lock/unlocking in loops. Must be called within lock of byteCache.
        /// </summary>
        /// <returns></returns>
        static byte UnsafeGetByte()
        {
            if (1 > remaining)
            {
                DirectGetBytes(byteCache);
                lastPosition = 0;
                remaining = byteCache.Length;
            }

            lastPosition++;
            remaining--;
            return byteCache[lastPosition - 1];
        }

        /// <summary>
        /// Rejects bytes which are equal to or greater than max. This is useful for ensuring there is no bias when you are modulating with a non power of 2 number.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="max"></param>
        public static void GetBytesWithMax(byte[] buffer, byte max)
        {
            if (buffer.Length > byteCache.Length / 2) //No point caching for larger sizes
            {
                DirectGetBytes(buffer);

                lock (byteCache)
                {
                    UnsafeCheckBytesMax(buffer, max);
                }
            }
            else
            {
                lock (byteCache)
                {
                    if (buffer.Length > remaining) //Recache if not enough remaining, discarding remaining - too much work to join two blocks
                        DirectGetBytes(byteCache);

                    Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
                    lastPosition += buffer.Length;
                    remaining -= buffer.Length;

                    UnsafeCheckBytesMax(buffer, max);
                }
            }
        }

        /// <summary>
        /// Checks buffer for bytes equal and above max. Must be called within lock of byteCache.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="max"></param>
        static void UnsafeCheckBytesMax(byte[] buffer, byte max)
        {
            for (int i = 0; i < buffer.Length; i++)
            {
                while (buffer[i] >= max)
                    buffer[i] = UnsafeGetByte(); //Replace all bytes which are equal or above max
            }
        }
    }

    对于历史-我以前的答案,使用随机对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
        private static char[] charSet =
         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();

        static rGen = new Random(); //Must share, because the clock seed only has Ticks (~10ms) resolution, yet lock has only 20-50ns delay.
        static int byteSize = 256; //Labelling convenience
        static int biasZone = byteSize - (byteSize % charSet.Length);
        static bool SlightlyMoreSecurityNeeded = true; //Configuration - needs to be true, if more security is desired and if charSet.Length is not divisible by 2^X.
        public string GenerateRandomString(int Length) //Configurable output string length
        {
          byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
          char[] rName = new char[Length];
          lock (rGen) //~20-50ns
          {
              rGen.NextBytes(rBytes);

              for (int i = 0; i < Length; i++)
              {
                  while (SlightlyMoreSecurityNeeded && rBytes[i] >= biasZone) //Secure against 1/5 increased bias of index[0-7] values against others. Note: Must exclude where it == biasZone (that is >=), otherwise there's still a bias on index 0.
                      rBytes[i] = rGen.NextByte();
                  rName[i] = charSet[rBytes[i] % charSet.Length];
              }
          }
          return new string(rName);
        }

    性能:

  • SecureFastRandom-第一次单次运行=~9-33ms。不可察觉。正在进行:在10000次迭代中持续5毫秒(有时可达13毫秒),单个平均迭代=1.5微秒。注:通常需要2个缓存刷新,但有时最多需要8个缓存刷新-取决于超过偏差区域的单个字节数
  • 随机-第一次单次运行=~0-1毫秒。不可察觉。正在进行:5毫秒,超过10000次迭代。单次平均迭代=0.5微秒。速度差不多。
  • 还检查:

    • https://bitback.org/merarischroeder/number-range-with-no-bias/src
    • https://stackoverflow.com/a/45118325/887092

    这些链接是另一种方法。缓冲可以添加到这个新的代码库中,但最重要的是探索不同的方法来消除偏差,并对速度和优缺点进行基准测试。


    我在寻找一个更具体的答案,在这里我想控制随机字符串的格式,并遇到了这篇文章。例如:车牌(汽车)有特定的格式(每个国家),我想创建随机的车牌。我决定为此编写自己的随机扩展方法。(这是为了重用同一个随机对象,因为在多线程场景中可以使用双精度对象)。我创建了一个gist(https://gist.github.com/samvanhoutte/808845ca78b9c041e928),但也将在这里复制扩展类:

    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
    void Main()
    {
        Random rnd = new Random();
        rnd.GetString("1-###-000").Dump();
    }

    public static class RandomExtensions
    {
        public static string GetString(this Random random, string format)
        {
            // Based on http://stackoverflow.com/questions/1344221/how-can-i-generate-random-alphanumeric-strings-in-c
            // Added logic to specify the format of the random string (# will be random string, 0 will be random numeric, other characters remain)
            StringBuilder result = new StringBuilder();
            for(int formatIndex = 0; formatIndex < format.Length ; formatIndex++)
            {
                switch(format.ToUpper()[formatIndex])
                {
                    case '0': result.Append(getRandomNumeric(random)); break;
                    case '#': result.Append(getRandomCharacter(random)); break;
                    default : result.Append(format[formatIndex]); break;
                }
            }
            return result.ToString();
        }

        private static char getRandomCharacter(Random random)
        {
            string chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            return chars[random.Next(chars.Length)];
        }

        private static char getRandomNumeric(Random random)
        {
            string nums ="0123456789";
            return nums[random.Next(nums.Length)];
        }
    }


    尝试将两部分结合起来:唯一(序列、计数器或日期)和随机

    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
    public class RandomStringGenerator
    {
        public static string Gen()
        {
            return ConvertToBase(DateTime.UtcNow.ToFileTimeUtc()) + GenRandomStrings(5); //keep length fixed at least of one part
        }

        private static string GenRandomStrings(int strLen)
        {
            var result = string.Empty;

            var Gen = new RNGCryptoServiceProvider();
            var data = new byte[1];

            while (result.Length < strLen)
            {
                Gen.GetNonZeroBytes(data);
                int code = data[0];
                if (code > 48 && code < 57 || // 0-9
                    code > 65 && code < 90 || // A-Z
                    code > 97 && code < 122   // a-z
                    )
                {
                    result += Convert.ToChar(code);
                }
            }

            return result;
        }

        private static string ConvertToBase(long num, int nbase = 36)
        {
            var chars ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //if you wish make algoritm more secure - change order of letter here

            // check if we can convert to another base
            if (nbase < 2 || nbase > chars.Length)
                return null;

            int r;
            var newNumber = string.Empty;

            // in r we have the offset of the char that was converted to the new base
            while (num >= nbase)
            {
                r = (int) (num % nbase);
                newNumber = chars[r] + newNumber;
                num = num / nbase;
            }
            // the last number to convert
            newNumber = chars[(int)num] + newNumber;

            return newNumber;
        }
    }

    测验:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [Test]
        public void Generator_Should_BeUnigue1()
        {
            //Given
            var loop = Enumerable.Range(0, 1000);
            //When
            var str = loop.Select(x=> RandomStringGenerator.Gen());
            //Then
            var distinct = str.Distinct();
            Assert.AreEqual(loop.Count(),distinct.Count()); // Or Assert.IsTrue(distinct.Count() < 0.95 * loop.Count())
        }


    以下是Eric J解决方案的变体,即针对WinRT(Windows应用商店应用程序)的密码声音:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static string GenerateRandomString(int length)
    {
        var chars ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        var result = new StringBuilder(length);
        for (int i = 0; i < length; ++i)
        {
            result.Append(CryptographicBuffer.GenerateRandomNumber() % chars.Length);
        }
        return result.ToString();
    }

    如果性能很重要(尤其是长度很长时):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static string GenerateRandomString(int length)
    {
        var chars ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        var result = new System.Text.StringBuilder(length);
        var bytes = CryptographicBuffer.GenerateRandom((uint)length * 4).ToArray();
        for (int i = 0; i < bytes.Length; i += 4)
        {
            result.Append(BitConverter.ToUInt32(bytes, i) % chars.Length);
        }
        return result.ToString();
    }


    不使用Random的解决方案:

    1
    2
    3
    4
    5
    var chars = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8);

    var randomStr = new string(chars.SelectMany(str => str)
                                    .OrderBy(c => Guid.NewGuid())
                                    .Take(8).ToArray());


    现在在一个线性风味。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private string RandomName
        {
            get
            {
                return new string(
                    Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                        .Select(s =>
                        {
                            var cryptoResult = new byte[4];
                            using (var cryptoProvider = new RNGCryptoServiceProvider())
                                cryptoProvider.GetBytes(cryptoResult);
                            return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                        })
                        .ToArray());
            }
        }


    我知道这不是最好的办法。但你可以试试这个。

    1
    2
    3
    string str = Path.GetRandomFileName(); //This method returns a random file name of 11 characters
    str = str.Replace(".","");
    Console.WriteLine("Random string:" + str);


    我不知道这在密码学上听起来怎么样,但它比迄今为止更复杂的解决方案(IMO)更可读、更简洁,而且它应该比基于System.Random的解决方案更"随机"。

    1
    2
    3
    4
    5
    6
    7
    return alphabet
        .OrderBy(c => Guid.NewGuid())
        .Take(strLength)
        .Aggregate(
            new StringBuilder(),
            (builder, c) => builder.Append(c))
        .ToString();

    我不能决定这个版本还是下一个版本更"漂亮",但它们给出的结果完全相同:

    1
    2
    3
    4
    return new string(alphabet
        .OrderBy(o => Guid.NewGuid())
        .Take(strLength)
        .ToArray());

    当然,它并没有针对速度进行优化,所以如果每秒生成数百万个随机字符串是任务关键,请尝试另一个!

    注意:这个解决方案不允许重复字母表中的符号,而且字母表的大小必须等于或大于输出字符串,这使得这种方法在某些情况下不太可取,这完全取决于您的用例。


    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
    public static class StringHelper
    {
        private static readonly Random random = new Random();

        private const int randomSymbolsDefaultCount = 8;
        private const string availableChars ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        private static int randomSymbolsIndex = 0;

        public static string GetRandomSymbols()
        {
            return GetRandomSymbols(randomSymbolsDefaultCount);
        }

        public static string GetRandomSymbols(int count)
        {
            var index = randomSymbolsIndex;
            var result = new string(
                Enumerable.Repeat(availableChars, count)
                          .Select(s => {
                              index += random.Next(s.Length);
                              if (index >= s.Length)
                                  index -= s.Length;
                              return s[index];
                          })
                          .ToArray());
            randomSymbolsIndex = index;
            return result;
        }
    }


    这里有一种生成随机字母数字字符串的机制(我使用它来生成密码和测试数据),而不定义字母和数字,

    cleanupbase64将删除字符串中的必要部分,并继续递归地添加随机字母和数字。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
            public static string GenerateRandomString(int length)
            {
                var numArray = new byte[length];
                new RNGCryptoServiceProvider().GetBytes(numArray);
                return CleanUpBase64String(Convert.ToBase64String(numArray), length);
            }

            private static string CleanUpBase64String(string input, int maxLength)
            {
                input = input.Replace("-","");
                input = input.Replace("=","");
                input = input.Replace("/","");
                input = input.Replace("+","");
                input = input.Replace("","");
                while (input.Length < maxLength)
                    input = input + GenerateRandomString(maxLength);
                return input.Length <= maxLength ?
                    input.ToUpper() : //In my case I want capital letters
                    input.ToUpper().Substring(0, maxLength);
            }


    如果您的值不是完全随机的,但事实上可能取决于某个东西——您可以计算该"somwthing"的md5或sha1散列,然后将其截断为所需的长度。

    也可以生成和截断一个guid。


    非常简单的解决方案。它使用ASCII值,只在它们之间生成"随机"字符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static class UsernameTools
    {
        public static string GenerateRandomUsername(int length = 10)
        {
            Random random = new Random();
            StringBuilder sbuilder = new StringBuilder();
            for (int x = 0; x < length; ++x)
            {
                sbuilder.Append((char)random.Next(33, 126));
            }
            return sbuilder.ToString();
        }

    }

    您只需使用组件SRVTextToImage。并在下面编写代码以生成随机字符串。

    1
    2
    CaptchaRandomImage c1 = new CaptchaRandomImage();
                string text = c1.GetRandomString(8);

    主要用于验证码的实现。但在你的情况下,它也起作用。希望它有帮助。