首先,您链接的演示文稿仅讨论了出于安全目的的随机数。所以它并没有声称Random 对非安全目的不利。
但我确实声称它是。 Random 的 .net 4 实现在几个方面存在缺陷。我建议仅在您不关心随机数的质量时才使用它。我建议使用更好的第三方实现。
缺陷 1:播种
带有当前时间的默认构造函数种子。因此,在短时间内(大约 10 毫秒)使用默认构造函数创建的所有 Random 实例都返回相同的序列。这是记录和“设计”的。如果您想对代码进行多线程处理,这尤其令人讨厌,因为您不能简单地在每个线程执行开始时创建 Random 的实例。
解决方法是在使用默认构造函数时要格外小心,并在必要时手动设置种子。
这里的另一个问题是种子空间相当小(31 位)。因此,如果您使用完全随机的种子生成 50k 个Random 实例,您可能会两次获得一个随机数序列(由于birthday paradox)。所以人工播种也不容易做好。
缺陷二:Next(int maxValue)返回的随机数分布有偏差
Next(int maxValue) 的某些参数显然不统一。例如,如果您计算 r.Next(1431655765) % 2,您将在大约 2/3 的样本中得到 0。 (答案末尾的示例代码。)
缺陷 3:NextBytes() 方法效率低。
NextBytes() 的每字节成本大约与使用Next() 生成完整整数样本的成本一样大。由此我怀疑他们确实每个字节创建一个样本。
在每个样本中使用 3 个字节的更好实现将使 NextBytes() 的速度提高近 3 倍。
由于这个缺陷,Random.NextBytes() 在我的机器(Win7,Core i3 2600MHz)上仅比System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes 快约 25%。
我敢肯定,如果有人检查了源代码/反编译的字节码,他们会发现比我在黑盒分析中发现的更多的缺陷。
代码示例
r.Next(0x55555555) % 2 有强烈的偏见:
Random r = new Random();
const int mod = 2;
int[] hist = new int[mod];
for(int i = 0; i < 10000000; i++)
{
int num = r.Next(0x55555555);
int num2 = num % 2;
hist[num2]++;
}
for(int i=0;i<mod;i++)
Console.WriteLine(hist[i]);
性能:
byte[] bytes=new byte[8*1024];
var cr=new System.Security.Cryptography.RNGCryptoServiceProvider();
Random r=new Random();
// Random.NextBytes
for(int i=0;i<100000;i++)
{
r.NextBytes(bytes);
}
//One sample per byte
for(int i=0;i<100000;i++)
{
for(int j=0;j<bytes.Length;j++)
bytes[j]=(byte)r.Next();
}
//One sample per 3 bytes
for(int i=0;i<100000;i++)
{
for(int j=0;j+2<bytes.Length;j+=3)
{
int num=r.Next();
bytes[j+2]=(byte)(num>>16);
bytes[j+1]=(byte)(num>>8);
bytes[j]=(byte)num;
}
//Yes I know I'm not handling the last few bytes, but that won't have a noticeable impact on performance
}
//Crypto
for(int i=0;i<100000;i++)
{
cr.GetBytes(bytes);
}