【问题标题】:Random String Generator creates same string on multiple calls随机字符串生成器在多个调用中创建相同的字符串
【发布时间】:2011-02-14 20:34:12
【问题描述】:

我已经构建了一个随机字符串生成器,但是我遇到了一个问题,即如果我在 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$

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

【问题讨论】:

    标签: asp.net vb.net string random


    【解决方案1】:

    这样做的原因是,当您构造 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;
                }
            }
        }
    }
    

    这可能会使两个线程开始时彼此非常接近以使用相同的值作为种子,因此需要权衡。

    【讨论】:

    • 我认为可能是这样。我不在乎它是否“可预测”,但我想知道您所说的“种子”是什么意思。使用System.Threading.Thread.Sleep(10) 或类似的东西就足够了吗?
    • 这会减慢您的程序,您真的要这样做吗?
    • 你不能将该函数包装在一个类中,将 Random 对象作为一个字段吗?
    • 我不想放慢速度,但更不想拥有相同的随机字符串。避免两者的最佳方法是什么?我完全被难住了。
    • @Matteo,该函数在一个类中......它实际上在一个 ClassLibrary 中,我每次调用它时都会启动一个新的对象实例(上面已编辑)。它没有任何区别。休眠电话确实可以解决问题,但正如@Lasse 指出的那样,它确实减慢了速度。
    【解决方案2】:

    唯一种子编号方法

    为了防止使用相同的种子值,为了停止生成相同的随机序列,您可以通过使用如下函数将 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
    

    使用返回的 int 作为随机数生成器的种子。

    现在使用自定义函数GetNewSeed解决了这个问题。

    Dim rnd1 As New Random( GetNewSeed )
    

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

    【讨论】:

    • 除了我使用GenerateRandom 的方式是实际生成一个特定长度的字符串。 GenerateRandom(5) 创建一个长度为 5 个字符的字符串。
    • 缓存 Random 实例似乎仍然是最好的方法
    • @rockinthesixstring:我更正了第二个代码 sn-p。同意的缓存很好。此外,堆积种子值解决方案有助于确保在不同范围或程序集内创建的随机数生成器(或彼此不可见)不会因依赖时钟而遇到相同序列的相同问题 - 即多线程应用程序,而不是您的具体情况现在需要考虑这一点。
    • 是的,这似乎工作得很好,我“认为”我们也是线程安全的。好节目!
    【解决方案3】:

    如果您想要没有序列的真正随机性,那么绝对不要使用 System.Random 函数。最好使用 System.Security.Cryptography 函数,最好检查您的硬件是否支持 .NET 自动使用的 RNG(随机数生成)。

    这是一个很好的例子: http://www.obviex.com/Samples/Password.aspx

    【讨论】:

      【解决方案4】:

      我使用以下方法创建了一个独特的种子。

      Session["seedRandom"] = 1;
      

      在 page_load 处创建了一个会话变量。 此会话变量将递增并加起来为 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 + 更新的会话变量

      【讨论】:

        【解决方案5】:

        我做了一些更改,对我来说似乎很好。我不喜欢使用关键字作为变量名。注意我移动了随机语句:

        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-24
          • 2011-07-23
          相关资源
          最近更新 更多