热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#生成随机数的三种方法及其问题分析

本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。

随机数的定义为:产生的所有数字毫无关系.

在实际应用中很多地方会用到随机数,比如需要生成唯一的订单号.

在C#中获取随机数有三种方法:

一.Random 类

Random类默认的无参构造函数可以根据当前系统时钟为种子,进行一系列算法得出要求范围内的伪随机数.

这种随机数可以达到一些要求较低的目标,但是如果在高并发的情况下,Random类所取到的系统时钟种子接近甚至完全一样,就很有可能出现重复,这里用循环来举例

这个例子会输出10个相同的"随机数".

突显出的问题:因为Random进行伪随机数的算法是固定的,所以根据同一个种子计算出的数字必然是一样的.而以当代计算机的运行速度,该循环几乎是在瞬间完成的,种子一致,所以会出现10次循环输出同一随机数的情况.

有的时候使用random生成随机数的时候往往不是随机的 这是为什么呢?

随机数生成方法可以说是任何编程语言必备的功能,它的重要性不言而言,在C#中我们通常使用Random类生成随机数,在一些场景下,我却发现Random生成的随机数并不可靠,在下面的例子中我们通过循环随机生成5个随机数:

for (int i &#61; 0; i <5; i&#43;&#43;) { Random random &#61; new Random(); Console.WriteLine(random.Next()); }

这段代码执行后的结果如下所示&#xff1a;

2140400647 2140400647 2140400647 2140400647 2140400647

通过以上结果可知&#xff0c;随机数类生成了5个相同的数&#xff0c;这并非我们的预期&#xff0c;为什么呢&#xff1f;为了弄清楚这个问题&#xff0c;零度剖析了微软官方的开源Random类&#xff0c;发现在C#中生成随机数使用的算法是线性同余法&#xff0c;经百科而知&#xff0c;这种算法生成的不是绝对随机&#xff0c;而是一种伪随机数&#xff0c;线性同余法算法的的公式是&#xff1a;

第N&#43;1个数 &#61; ( 第N个数 * A &#43; B) % M

上面的公式中A、B和M分别为常数&#xff0c;是生成随机数的因子&#xff0c;如果之前从未通过同一个Random对象生成过随机数(也就是调用过Next方法)&#xff0c;那么第N个随机数为将被指定为一个默认的常数&#xff0c;这个常数在创建一个Random类时被默认值指定&#xff0c;Random也提供一个构造函数允许开发者使用自己的随机数因子&#xff0c;这一切可通过微软官方开源代码看到&#xff1a;

public Random() : this(Environment.TickCount) { } public Random(int Seed) { }

通过默认构造函数创建Random类时&#xff0c;一个Environment.TickCount对象作为因子被默认传递给第二个构造函数&#xff0c;Environment.TickCount表示操作系统启动后经过的毫秒数&#xff0c;计算机的运算运算速度远比毫秒要快得多&#xff0c;这导致一个的具有毫秒精度的因子参与随机数的生成过程&#xff0c;但在5次循环中&#xff0c;我们使用了同一个毫秒级的因子&#xff0c;从而生成相同的随机数&#xff0c;另外&#xff0c;第N&#43;1个数的生成与第N个数有着直接的关系。

在上面的例子中&#xff0c;假设系统启动以来的毫秒数为888毫秒&#xff0c;执行5次循环用时只有0.1毫秒&#xff0c;这导致在循环中创建的5个Random对象都使用了相同的888因子&#xff0c;每次被创建的随机对象又使用了相同的第N个数(默认为常数)&#xff0c;通过这样的假设我们不难看出&#xff0c;上面的结果是必然的。

现在我们改变这个格局&#xff0c;在循环之外创建一个Random对象&#xff0c;在每次循环中引用它&#xff0c;并通过它生成随机数&#xff0c;并在同一个对象上多次调用Next方法&#xff0c;从而不断变化第N个数&#xff0c;代码如下所示&#xff1a;

Random random &#61; new Random(); for (int i &#61; 0; i <5; i&#43;&#43;) { Console.WriteLine(random.Next()); }

执行后的结果如下所示&#xff1a;

391098894 1791722821 1488616582 1970032058 201874423

我们看到这个结果确实证实了我们上面的推断&#xff0c;第1次循环时公式中的第N个数为默认常数&#xff1b;当第二次循环时&#xff0c;第N个数为391098894&#xff0c;随后不断变化的第N个数作为因子参与计算&#xff0c;这保证了结果的随机性。

虽然通过我们的随机数看起来也很随机了&#xff0c;但必定这个算法是伪随机数&#xff0c;当第N个数和因子都相同时&#xff0c;生成的随机数仍然是重复的随机数&#xff0c;由于Random提供一个带参的构造函数允许我们传入一个因子&#xff0c;如果传入的因子随机性强的话&#xff0c;那么生成的随机数也会比较可靠&#xff0c;为了提供一个可靠点的因子&#xff0c;我们通常使用GUID产生填充因子&#xff0c;同样放在循环中测试&#xff1a;

for (int i &#61; 0; i <5; i&#43;&#43;) { byte[] buffer &#61; Guid.NewGuid().ToByteArray(); int iSeed &#61; BitConverter.ToInt32(buffer, 0); Random random &#61; new Random(iSeed); Console.WriteLine(random.Next()); }

这样的方式保证了填充因子的随机性&#xff0c;所以生成的随机数也比较可靠&#xff0c;运行结果如下所示&#xff1a;

734397360 1712793171 1984332878 819811856 1015979983

在一些场景下这样的随机数并不可靠&#xff0c;为了生成更加可靠的随机数&#xff0c;微软在System.Security.Cryptography命名空间下提供一个名为RNGCryptoServiceProvider的类&#xff0c;它采用系统当前的硬件信息、进程信息、线程信息、系统启动时间和当前精确时间作为填充因子&#xff0c;通过更好的算法生成高质量的随机数&#xff0c;它的使用方法如下所示&#xff1a;

byte[] randomBytes &#61; new byte[4]; RNGCryptoServiceProvider rngServiceProvider &#61; new RNGCryptoServiceProvider(); rngServiceProvider.GetBytes(randomBytes); Int32 result &#61; BitConverter.ToInt32(randomBytes, 0);

通过这种算法生成的随机数&#xff0c;经过成千上万次的测试&#xff0c;并未发现重复&#xff0c;质量的确比Random高了很多。另外windows api也提供了一个非托管的随机数生成函数CryptGenRandom&#xff0c;CryptGenRandom与RNGCryptoServiceProvider的原理类似&#xff0c;采用C&#43;&#43;编写&#xff0c;如果要在.NET中使用&#xff0c;需要进行简单的封装。它的原型如下所示&#xff1a;

BOOL WINAPI CryptGenRandom( _In_ HCRYPTPROV hProv, _In_ DWORD dwLen, _Inout_ BYTE *pbBuffer );

以上就是零度为您带来的随机数生成方法和基本原理&#xff0c;您可以通过需求和场景选择最佳的方式&#xff0c;Random算法简单&#xff0c;性能较高&#xff0c;适用于随机性要求不高的情况&#xff0c;由于RNGCryptoServiceProvider在生成期间需要查询上面提到的几种系统因子&#xff0c;所以性能稍弱于Random类&#xff0c;但随机数质量高&#xff0c;可靠性更好。

二.Guid 类

System.Guid

GUID (Globally Unique Identifier) 全球唯一标识符

GUID的计算使用到了很多在本机可取到的数字,如硬件的ID码,当前时间等.所计算出的128位整数(16字节)可以接近唯一的输出.

计算结果是xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx结构的16进制数字.当然这个格式也是可以更改的.

三.RNGCryptoServiceProvider 类

System.Security.Cryptography.RNGCryptoServiceProvider

RNGCryptoServiceProvider 使用加密服务提供程序 (CSP) 提供的实现来实现加密随机数生成器 (RNG)

因该类使用更严密的算法.所以即使如下放在循环中,所计算出的随机数也是不同的.

Membership.GeneratePassword()

Membership是一个方便快捷的进行角色权限管理的类,偶然发现一个很有意思的方法,没研究过是如何实现的

例:

结果为

C!&^HoTNv3!ZHkK9BAbu

azLgER)JJ-UW8q*14yz*

I3qnb]Zxu16ht!kKZ!Q*

9U:MAQ&c1x)^aed&#64;xe**

oL(%4JvfbP&t5*Hpl4l-

6&#64;zj$CnhW&D&#43;|xOf:qIk

A/!Di&l*tY$QaMH0gyzY

z^wu6{1BMq7D^&#43;WU]>f$

1OgIJS3&09fw0F9.|aXA

8F&#43;Gy&#43;L{O6x{SfugME*%



推荐阅读
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 在前两篇文章中,我们探讨了 ControllerDescriptor 和 ActionDescriptor 这两个描述对象,分别对应控制器和操作方法。本文将基于 MVC3 源码进一步分析 ParameterDescriptor,即用于描述 Action 方法参数的对象,并详细介绍其工作原理。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 如何在窗口右下角添加调整大小的手柄
    本文探讨了如何在传统MFC/Win32 API编程中实现类似C# WinForms中的SizeGrip功能,即在窗口的右下角显示一个用于调整窗口大小的手柄。我们将介绍具体的实现方法和相关API。 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
author-avatar
QEWERTGF_978
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有