为什么不是使用RandomNumberGenerator随机分配字符的Base36随机字符串

时间:2019-05-14 10:57:42

标签: c# random base36

我正在尝试使用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();
}

这似乎可行,但是从结果来看,字符串显然没有均匀分布潜在字符。许多重复的字母和数字很少出现。

问题是仅采用随机字符串的第一个字符还是字符串的构建方式有问题?

1 个答案:

答案 0 :(得分:2)

我想如果要坚持这种方法,应该使用模数而不是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();
}