随机字符串生成器在多次调用时创建相同的字符串

5

我已经编写了一个随机字符串生成器,但是我遇到了一个问题,如果我在Page_Load方法中多次调用该函数,函数会返回相同的字符串两次。

以下是代码:

Public Class CustomStrings
    ''' <summary>'
    ''' Generates a Random String'
    ''' </summary>'
    ''' <param name="n">number of characters the method should generate</param>'
    ''' <param name="UseSpecial">should the method include special characters? IE: # ,$, !, etc.</param>'
    ''' <param name="SpecialOnly">should the method include only the special characters and excludes alpha numeric</param>'
    ''' <returns>a random string n characters long</returns>'
    Public Function GenerateRandom(ByVal n As Integer, Optional ByVal UseSpecial As Boolean = True, Optional ByVal SpecialOnly As Boolean = False) As String

        Dim chars As String() ' a character array to use when generating a random string'
        Dim ichars As Integer = 74 'number of characters to use out of the chars string'
        Dim schars As Integer = 0 ' number of characters to skip out of the characters string'

        chars = { _
         "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", _
         "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", "!", "@", "#", "$", _
         "%", "^", "&", "*", "(", ")", _
         "-", "+"}


        If Not UseSpecial Then ichars = 62 ' only use the alpha numeric characters out of "char"'
        If SpecialOnly Then schars = 62 : ichars = 74 ' skip the alpha numeric characters out of "char"'

        Dim rnd As New Random()
        Dim random As String = String.Empty
        Dim i As Integer = 0
        While i < n
            random += chars(rnd.[Next](schars, ichars))
            System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)
        End While
        rnd = Nothing
        Return random
    End Function
End Class

但如果我像这样调用某个东西

    Dim rnd1 As New CustomStrings
    Dim rnd2 As New CustomStrings

    Dim str1 As String = rnd1.GenerateRandom(5) 
    Dim str2 As String = rnd2.GenerateRandom(5) 

    rnd1 = Nothing
    rnd2 = Nothing

响应将类似于以下内容:

g*3Jq
g*3Jq

第二次调用时,它将是:

3QM0$
3QM0$

我错过了什么?我希望每个随机字符串都是唯一的。

5个回答

7
这是因为当您构造Random类的实例时,它会从时钟生成种子,但此时钟的准确性不足以在每次快速调用时产生新的种子。

换句话说,这样做:
Random r = new Random();
int i = r.Next(1000);
r = new Random();
int j = r.Next(1000);

这段代码很有可能会在ij中产生相同的值。

你需要做的是:

  • 创建并缓存一个Random实例,使其在每次调用时使用相同的实例(但不幸的是该类不是线程安全的,所以至少保留每个线程的缓存副本)
  • 使用某些随机数种子对其进行初始化,以便每次调用都会改变(这是相当困难的,因为使用顺序值对其进行初始化将产生可预测的随机数)

这里是一个示例程序,它为每个线程创建一个单独的Random实例,并从全局随机对象中进行初始化。同样地,这可能会产生可预测的序列。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SO2755146
{
    public class Program
    {
        public static void Main()
        {
            List<Task> tasks = new List<Task>();
            for (int index = 0; index < 1000; index++)
                tasks.Add(Task.Factory.StartNew(() => Console.Out.WriteLine(RNG.Instance.Next(1000))));
            Task.WaitAll(tasks.ToArray());
        }
    }

    public static class RNG
    {
        private static Random _GlobalSeed = new Random();
        private static object _GlobalSeedLock = new object();

        [ThreadStatic]
        private static Random _Instance;

        public static Random Instance
        {
            get
            {
                if (_Instance == null)
                {
                    lock (_GlobalSeedLock)
                    {
                        _Instance = new Random(_GlobalSeed.Next());
                    }
                }
                return _Instance;
            }
        }
    }
}

如果您只想从时钟中种植每个随机实例,但至少每个线程产生不同的随机序列,可以简化如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SO2755146
{
    public class Program
    {
        public static void Main()
        {
            List<Task> tasks = new List<Task>();
            for (int index = 0; index < 1000; index++)
                tasks.Add(Task.Factory.StartNew(() => Console.Out.WriteLine(RNG.Instance.Next(1000))));
            Task.WaitAll(tasks.ToArray());
        }
    }

    public static class RNG
    {
        [ThreadStatic]
        private static Random _Instance;

        public static Random Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new Random();

                return _Instance;
            }
        }
    }
}

这可能会使非常接近启动的两个线程使用相同的值进行种子化,因此存在权衡。


那会减慢你的程序速度,你真的想这样做吗? - Lasse V. Karlsen
1
你不能将该函数封装在一个类中,将 Random 对象作为字段保留吗? - Matteo Italia
不可以,你只能使用 Int32 数字来进行种子初始化。 - Lasse V. Karlsen
那么,你应该将它声明为全局或静态对象之类的东西,以保留Random对象的状态。 - Matteo Italia
FYI:它可以使用GUID(类似)进行种子化。static readonly Random Rand = new Random(Guid.NewGuid().GetHashCode()); - Chase Florell
显示剩余8条评论

1

我使用了下面的方法来创建一个唯一的种子。

Session["seedRandom"] = 1;
在页面加载时创建了一个会话变量。 这个会话变量将递增并加上DateTime.Now.Ticks。
private string getRandAlphaNum()
{
   int seed_value = (int)DateTime.Now.Ticks;
   seed_value = seed_value + Int32.Parse(Session["seedRandom"].ToString());
   //change the Session variable by incrementing its value to 1 after creating seed value.
   Session["seedRandom"] = Int32.Parse(Session["seedRandom"].ToString()) + 1;

   Random rand = new Random(seed_value);
   .....
   .....
}
每次我都会得到一个种子,它是由DateTimeNow.Ticks + 更新的会话变量组成的。

1

独特的种子数方法

为了防止使用相同的种子值,以停止生成相同的随机序列,您可以通过将GUID(隐式随机化)沸腾成int值来创建随机种子,使用以下函数:

Private Function GetNewSeed() As Integer
    Dim arrBytes As Byte() = Guid.NewGuid().ToByteArray()  '16 bytes
    Dim seedNum As Integer = 0
    ' Boil GUID down 4 bytes at a time (size of int) and merge into Integer value
    For i As Integer = 0 To arrBytes.Length - 1 Step 4
        seedNum = seedNum Xor BitConverter.ToInt32(arrBytes, i)
    Next
    Return seedNum
End Function

使用返回的整数来初始化您的随机数生成器。
现在通过使用自定义函数GetNewSeed解决了这个问题。
Dim rnd1 As New Random( GetNewSeed )

这解决了问题的根源,即种子值。


除了我使用GenerateRandom的方式是实际上生成一个特定长度的字符串之外。GenerateRandom(5)创建一个长度为5个字符的字符串。 - Chase Florell
将Random实例进行缓存仍然是最佳的做法。 - Chase Florell
@rockinthesixstring:我纠正了第二段代码片段。同意缓存是好的。此外,堆叠种子值解决方案有助于确保在不同作用域或程序集内创建的随机数生成器(或彼此不可见)不会因依赖时钟而遇到相同序列的问题 - 即多线程应用程序,尽管您的特定情况现在不需要考虑这个问题。 - John K
是的,那看起来运作得很好,而且我认为我们也确保了线程安全性。干得好! - Chase Florell

1
如果您想要没有序列的真正随机性,那么绝对不要使用System.Random函数。最好使用System.Security.Cryptography函数,并且最好检查您的硬件是否具有RNG(随机数生成)支持,.NET将自动利用它。
这是一个很好的例子: http://www.obviex.com/Samples/Password.aspx

0

我做了一些更改,现在看起来很好。我不喜欢将关键字用作变量名。请注意,我移动了随机语句:

Public Class Form1

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim myCS As New CustomStrings
    Dim l As New List(Of String)

    For x As Integer = 1 To 10
        Dim s As String = myCS.GenerateRandom(5)
        l.Add(s)
    Next

    For x As Integer = 0 To l.Count - 1
        Debug.WriteLine(l(x))
    Next
    'Debug output
    'YGXiV
    'rfLmP
    'OVUW9
    '$uaMt
    '^RsPz
    'k&91k
    '(n2uN
    'ldbQQ
    'zYlP!
    '30kNt
End Sub

Public Class CustomStrings

    Private myRnd As New Random()
    Public Function GenerateRandom(ByVal n As Integer, _
                                   Optional ByVal UseSpecial As Boolean = True, _
                                   Optional ByVal SpecialOnly As Boolean = False) As String

        Dim ichars As Integer = 74 'number of characters to use out of the chars string'
        Dim schars As Integer = 0 ' number of characters to skip out of the characters string'

        Dim chars() As Char = New Char() {"A"c, "B"c, "C"c, "D"c, "E"c, "F"c, _
                                          "G"c, "H"c, "I"c, "J"c, "K"c, "L"c, _
                                          "M"c, "N"c, "O"c, "P"c, "Q"c, "R"c, _
                                          "S"c, "T"c, "U"c, "V"c, "W"c, "X"c, _
                                          "Y"c, "Z"c, "0"c, "1"c, "2"c, "3"c, _
                                          "4"c, "5"c, "6"c, "7"c, "8"c, "9"c, _
                                          "a"c, "b"c, "c"c, "d"c, "e"c, "f"c, _
                                          "g"c, "h"c, "i"c, "j"c, "k"c, "l"c, _
                                          "m"c, "n"c, "o"c, "p"c, "q"c, "r"c, _
                                          "s"c, "t"c, "u"c, "v"c, "w"c, "x"c, _
                                          "y"c, "z"c, "!"c, "@"c, "#"c, "$"c, _
                                          "%"c, "^"c, "&"c, "*"c, "("c, ")"c, _
                                          "-"c, "+"c}


        If Not UseSpecial Then ichars = 62 ' only use the alpha numeric characters out of "char"'
        If SpecialOnly Then schars = 62 : ichars = 74 ' skip the alpha numeric characters out of "char"'

        Dim rndStr As String = String.Empty
        Dim i As Integer = 0
        While i < n
            rndStr += chars(Me.myRnd.Next(schars, ichars))
            System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)
        End While
        Return rndStr
    End Function
End Class

End Class

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