为什么这个 Base36 随机字符串不使用 RandomNumberGenerator 随机分配字符
Why isn't this Base36 random string using RandomNumberGenerator randomly distributing the characters
我正在尝试使用 C#
生成一个随机的 Base36 字符串。我使用 RandomNumberGenerator
而不是 Random
因为代码需要是线程安全的。我有以下代码设置:
private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();
private string GenerateBase36Token(int length)
{
string token = string.Empty;
for (int i = 0; i < length; i++)
{
byte[] bytes = new byte[100];
_random.GetBytes(bytes);
token += ToBase36String(bytes)[0];
}
return token;
}
private string ToBase36String(byte[] toConvert)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
BigInteger dividend = new BigInteger(toConvert);
StringBuilder builder = new StringBuilder();
while (dividend != 0)
{
BigInteger remainder;
dividend = BigInteger.DivRem(dividend, alphabet.Length, out remainder);
builder.Insert(0, alphabet[Math.Abs((int)remainder)]);
}
return builder.ToString();
}
这似乎可行,但从结果来看,很明显字符串并未均匀分布潜在字符。有很多重复的字母和数字很少出现。
问题是随机字符串的第一个字符,还是字符串的构建方式有问题?
如果您想坚持使用这种方法,我认为您应该使用模数而不是 DivRem。我这样做的动机是,如果你一直用一个大数除以一个小数,你会得到这样一种情况,即原始数字相对较高或较低(即 100,相对于一个大数)无关紧要).
例如,将这些数字作为输入(仅作为示例):
36.000.000 作为您的股息,以及
10 作为你的除数。
ToBase36String 中的 while 循环将如下所示:
迭代 1:
股息:36.000.000
余数:3.600.000
迭代 2:
股息:3.600.000
余数:360.000
迭代 3:
分红:360.000
余数:36.000
迭代 4:
分红:36.000
余数:3.600
迭代 5:
股息:3.600
余数:360
迭代 6:
分红:360
余数:36
迭代 7:
分红:36
余数:3
如果我们从 38.000.000 或 31.000.000 作为股息开始,那将无关紧要,因为由于整数除法的工作原理,迭代 7 无论如何都会得到 3 的余数。
我想表达的意思是,对我来说似乎没有必要为每个 base36 字符随机生成一个大于 36 的数字,而您的 GenerateBase36Token 方法会为每个字符创建 100 个字节的数据。
此外,我想知道为什么您需要 base36 字符,而 base64 是一种广泛使用和接受的数据编码格式。
tl;dr:一个快速简单的解决方案可能是只生成一个随机数据字节,并使用取模运算符而不是 DivRem 方法。
编辑:更新了您的代码
private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();
private string GenerateBase36Token(int length)
{
string token = string.Empty;
for (int i = 0; i < length; i++)
{
byte[] bytes = new byte[1]; //edited byte array size
_random.GetBytes(bytes);
token += ToBase36String(bytes)[0];
}
return token;
}
private string ToBase36String(byte[] toConvert)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int dividend = (int)toConvert[0];
StringBuilder builder = new StringBuilder();
int remainder;
remainder = dividend % alphabet.Length; //edited DivRem method usage to modulo operator usage
builder.Insert(0, alphabet[remainder]);
return builder.ToString();
}
我正在尝试使用 C#
生成一个随机的 Base36 字符串。我使用 RandomNumberGenerator
而不是 Random
因为代码需要是线程安全的。我有以下代码设置:
private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();
private string GenerateBase36Token(int length)
{
string token = string.Empty;
for (int i = 0; i < length; i++)
{
byte[] bytes = new byte[100];
_random.GetBytes(bytes);
token += ToBase36String(bytes)[0];
}
return token;
}
private string ToBase36String(byte[] toConvert)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
BigInteger dividend = new BigInteger(toConvert);
StringBuilder builder = new StringBuilder();
while (dividend != 0)
{
BigInteger remainder;
dividend = BigInteger.DivRem(dividend, alphabet.Length, out remainder);
builder.Insert(0, alphabet[Math.Abs((int)remainder)]);
}
return builder.ToString();
}
这似乎可行,但从结果来看,很明显字符串并未均匀分布潜在字符。有很多重复的字母和数字很少出现。
问题是随机字符串的第一个字符,还是字符串的构建方式有问题?
如果您想坚持使用这种方法,我认为您应该使用模数而不是 DivRem。我这样做的动机是,如果你一直用一个大数除以一个小数,你会得到这样一种情况,即原始数字相对较高或较低(即 100,相对于一个大数)无关紧要).
例如,将这些数字作为输入(仅作为示例): 36.000.000 作为您的股息,以及 10 作为你的除数。 ToBase36String 中的 while 循环将如下所示:
迭代 1: 股息:36.000.000 余数:3.600.000
迭代 2: 股息:3.600.000 余数:360.000
迭代 3: 分红:360.000 余数:36.000
迭代 4: 分红:36.000 余数:3.600
迭代 5: 股息:3.600 余数:360
迭代 6: 分红:360 余数:36
迭代 7: 分红:36 余数:3
如果我们从 38.000.000 或 31.000.000 作为股息开始,那将无关紧要,因为由于整数除法的工作原理,迭代 7 无论如何都会得到 3 的余数。
我想表达的意思是,对我来说似乎没有必要为每个 base36 字符随机生成一个大于 36 的数字,而您的 GenerateBase36Token 方法会为每个字符创建 100 个字节的数据。
此外,我想知道为什么您需要 base36 字符,而 base64 是一种广泛使用和接受的数据编码格式。
tl;dr:一个快速简单的解决方案可能是只生成一个随机数据字节,并使用取模运算符而不是 DivRem 方法。
编辑:更新了您的代码
private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();
private string GenerateBase36Token(int length)
{
string token = string.Empty;
for (int i = 0; i < length; i++)
{
byte[] bytes = new byte[1]; //edited byte array size
_random.GetBytes(bytes);
token += ToBase36String(bytes)[0];
}
return token;
}
private string ToBase36String(byte[] toConvert)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int dividend = (int)toConvert[0];
StringBuilder builder = new StringBuilder();
int remainder;
remainder = dividend % alphabet.Length; //edited DivRem method usage to modulo operator usage
builder.Insert(0, alphabet[remainder]);
return builder.ToString();
}