随机字符串生成器返回相同的字符串

230

我已经开发了一个随机字符串生成器,但它的表现并不完全如我所希望。我的目标是能够运行两次并生成两个不同的四个字符随机字符串。然而,它只生成了一个四个字符的随机字符串两次。

以下是代码和输出示例:

private string RandomString(int size)
{
    StringBuilder builder = new StringBuilder();
    Random random = new Random();
    char ch;
    for (int i = 0; i < size; i++)
    {
        ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));                 
        builder.Append(ch);
    }

    return builder.ToString();
}

// get 1st random string 
string Rand1 = RandomString(4);

// get 2nd random string 
string Rand2 = RandomString(4);

// create full rand string
string docNum = Rand1 + "-" + Rand2;

...输出结果看起来像这样: UNTE-UNTE ...但应该类似于这个样子 UNTE-FWNU

我如何确保两个截然不同的随机字符串?


良好的性能 - mola10
3
请注意,即使是两个完全随机的字符串也不能保证唯一。对于长字符串(120+位),它们非常可能是唯一的,但对于像这样的短字符串,碰撞是很常见的。 - CodesInChaos
虽然这是一个旧的线程,但如果适用的话,你可以生成一个GUID并将其转换为文本。 - user3657408
30个回答

313

您在方法中创建随机实例,这会导致在快速连续调用时返回相同的值。我会这样做:

private static Random random = new Random((int)DateTime.Now.Ticks);//thanks to McAden
private string RandomString(int size)
    {
        StringBuilder builder = new StringBuilder();
        char ch;
        for (int i = 0; i < size; i++)
        {
            ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));                 
            builder.Append(ch);
        }

        return builder.ToString();
    }

// get 1st random string 
string Rand1 = RandomString(4);

// get 2nd random string 
string Rand2 = RandomString(4);

// creat full rand string
string docNum = Rand1 + "-" + Rand2;

(你的代码的修改版)


47
请注意,Random 类的实例成员未被记录为线程安全,因此如果此方法同时从多个线程调用(例如制作 Web 应用程序时非常可能),则此代码的行为将是未定义的。您需要在随机数上使用锁或使其成为每个线程专用的。 - Greg Beech
19
通过使用 ch = (char)random.Next('A','Z'); 你可以更轻松地获得一个随机的大写字母,这要比原帖中难以阅读的代码行 ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); 简单得多。如果需要切换为小写字母,只需将其改为 (char)random.Next('a','z'); 即可。请注意保持翻译后的内容与原文意思相同,并尽可能使其更易于理解。 - Nick Freeman
17
@NickFreeman 提到,记住 [上限是不包括的](http://msdn.microsoft.com/en-us/library/2dx6wyd4.aspx),因此 ch = (char)random.Next('A','Z'); 永远不会返回 'Z'。因此你需要使用 ch = (char)random.Next('A', 'Z' + 1); 来包括 'Z' - T.J. Crowder
一种可能的 Linq 方法: Enumerable.Range(0,4).Select(c => (char)rand.Next('a', 'z' + 1)).ToArray(); - Jason Slocomb
1
我实际上使用 new Random(Guid.NewGuid().GetHashCode()); 作为种子,而不是依赖于 DateTime - Filipe Leite
显示剩余2条评论

190

您正在方法内实例化Random对象。

Random对象是从系统时钟种子化的,这意味着如果您快速连续调用该方法几次,它将每次使用相同的种子,这意味着它将生成相同的随机数序列,这意味着您将获得相同的字符串。

要解决问题,请将Random实例移到方法外部(同时还可以摆脱对ConvertFloorNextDouble的疯狂调用):

private readonly Random _rng = new Random();
private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

private string RandomString(int size)
{
    char[] buffer = new char[size];

    for (int i = 0; i < size; i++)
    {
        buffer[i] = _chars[_rng.Next(_chars.Length)];
    }
    return new string(buffer);
}

7
将其设为静态变量并将其限定在类内部。 - Jake Pearson
9
此外,我喜欢将这种方法作为Random的扩展方法。 - Cameron MacFarland
8
请注意,Random 类的实例成员未记录为线程安全,因此如果从多个线程同时调用此方法(例如在创建 Web 应用时),则此代码的行为将是未定义的。您需要在随机数生成器上使用锁或使其成为每个线程独立的。 - Greg Beech

138

一个非常简单的实现,它使用了Path.GetRandomFileName()

using System.IO;   
public static string RandomStr()
{
    string rStr = Path.GetRandomFileName();
    rStr = rStr.Replace(".", ""); // For Removing the .
    return rStr;
}

现在只需调用RandomStr()函数。


7
好的!当你发现像GetRandomFileName这样隐藏在.Net框架中的小宝石时,真是太棒了。 - Andy Britcliffe
2
使用您的代码并稍作修改。guid = Guid.NewGuid(); mystring = guid.ToString(); mystring = mystring.Replace("-", ""); mystring = mystring.Substring(0, 8); - Andrew
3
同意,我做了一个循环,在大约130,000次迭代后发生了碰撞。尽管有1,785,793,904,896个组合。 - Andrew
18
这将在磁盘上创建文件。根据MSDN的说明,如果使用GetTempFileName方法创建超过65535个文件而不删除以前的临时文件,则会引发IOException异常。如果不存在唯一的临时文件名,则GetTempFileName方法将引发IOException异常。 要解决此错误,请删除所有不需要的临时文件。 - bugnuker
25
“GetRandomFileName”方法返回一个加密强度高的随机字符串,可用作文件夹名称或文件名称。与“GetTempFileName”不同,“GetRandomFileName”不会创建文件。当您的文件系统安全至关重要时,应使用此方法而非“GetTempFileName”。我们正在讨论的是“GetRandomFileName()”而不是“GetTempFileName()”。 - quantum
显示剩余4条评论

49
只要您使用的是Asp.Net 2.0或更高版本,您也可以使用库调用System.Web.Security.Membership.GeneratePassword,但它将包括特殊字符。
要获取4个随机字符且至少有0个特殊字符:
Membership.GeneratePassword(4, 0)

9
请注意,4.0版本中的第二个整数参数表示要使用的最少数量的非字母数字字符。因此,Membership.GeneratePassword(10, 0);不会像你想象的那样工作,它仍然会添加大量的非字母数字字符,例如:z9sge)?pmV - keithl8041
我能想到不想使用这种方法的唯一理由就是你必须经历去除特殊字符的麻烦......假设你需要去除,但我并不确定。 - Ben
感谢keithl8041,已更新答案以反映这一点。 - Spongeboy
对我来说,只要您有会员访问权限,这就是正确的答案。我的意思是,为什么要重新发明热水呢? - Marko

23

仅供路过的人使用,如果想要在一行代码中生成随机字符串

int yourRandomStringLength = 12; //maximum: 32
Guid.NewGuid().ToString("N").Substring(0, yourRandomStringLength);

请记住,yourRandomStringLength的长度不能超过32,因为Guid的最大长度为32。


9
我不确定它一定会是随机的。GUID被设计为唯一的,而不是随机的,因此字符串中的前N个字符可能是相同的(取决于GUID生成器)。 - cdmckay
1
我只需要一个5位字符的临时密码进行哈希。这太好了,谢谢你。 - Bmo

14

这个解决方案是对 Random 类的扩展。

用法

class Program
{
    private static Random random = new Random(); 

    static void Main(string[] args)
    {
        random.NextString(10); // "cH*%I\fUWH0"
        random.NextString(10); // "Cw&N%27+EM"
        random.NextString(10); // "0LZ}nEJ}_-"
        random.NextString();   // "kFmeget80LZ}nEJ}_-"
    }
}

实现

public static class RandomEx
{
    /// <summary>
    /// Generates random string of printable ASCII symbols of a given length
    /// </summary>
    /// <param name="r">instance of the Random class</param>
    /// <param name="length">length of a random string</param>
    /// <returns>Random string of a given length</returns>
    public static string NextString(this Random r, int length)
    {
        var data = new byte[length];
        for (int i = 0; i < data.Length; i++)
        {
            // All ASCII symbols: printable and non-printable
            // data[i] = (byte)r.Next(0, 128);
            // Only printable ASCII
            data[i] = (byte)r.Next(32, 127);
        }
        var encoding = new ASCIIEncoding();
        return encoding.GetString(data);
    }

    /// <summary>
    /// Generates random string of printable ASCII symbols
    /// with random length of 10 to 20 chars
    /// </summary>
    /// <param name="r">instance of the Random class</param>
    /// <returns>Random string of a random length between 10 and 20 chars</returns>
    public static string NextString(this Random r)
    {
        int length  = r.Next(10, 21);
        return NextString(r, length);
    }
}

你从整个 ASCII 集合中提取,包括不可打印字符? - Nicholi
1
我已经添加了一个仅包含可打印符号的范围。您需要注释一行并取消注释另一行。谢谢。 - oleksii

12

这是另一种字符串生成器的版本。它很简单,没有花哨的数学和神奇的数字,但有一些指定允许字符的神奇字符串。

更新: 我将生成器设置为静态,因此在多次调用时不会返回相同的字符串。然而,这段代码不是线程安全的,绝对不是密码学安全的

要生成密码,请使用System.Security.Cryptography.RNGCryptoServiceProvider

private Random _random = new Random(Environment.TickCount);

public string RandomString(int length)
{
    string chars = "0123456789abcdefghijklmnopqrstuvwxyz";
    StringBuilder builder = new StringBuilder(length);

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

    return builder.ToString();
}

1
如果您多次调用它,此代码将生成相同的字符串。 - stian.net
感谢 @stian.net 的帮助,我将生成器设为静态的。 - Maksym Davydov

10

这里有一个额外的选项:

public System.String GetRandomString(System.Int32 length)
{
    System.Byte[] seedBuffer = new System.Byte[4];
    using (var rngCryptoServiceProvider = new System.Security.Cryptography.RNGCryptoServiceProvider())
    {
        rngCryptoServiceProvider.GetBytes(seedBuffer);
        System.String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        System.Random random = new System.Random(System.BitConverter.ToInt32(seedBuffer, 0));
        return new System.String(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
    }
}

7
最好的解决方案是将随机数生成器与base64转换结合使用。
public string GenRandString(int length)
{
  byte[] randBuffer = new byte[length];
  RandomNumberGenerator.Create().GetBytes(randBuffer);
  return System.Convert.ToBase64String(randBuffer).Remove(length);
}

请注意,结果可能包含「/」和「+」这两个符号。 - CodesInChaos
真的。但是我更喜欢这个,而不是Membership.GeneratePassword()。 - Aaron Azhari
返回 System.Convert.ToBase64String(randBuffer).Replace("/", "").Replace("+", "").Replace("=", "").Remove(length); - mjb

5

为了增加精度,可以使用下面的LINQ一行代码(假设有一个private static Random Random)...

public static string RandomString(int length)
{
    return new string(Enumerable.Range(0, length).Select(_ => (char)Random.Next('a', 'z')).ToArray());
}

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