这是我保证唯一性并引入一些随机性的方法。
- 使用保证提供唯一编号的序列生成器。由于您使用的是 SQL Server,因此这可以是
IDENTITY 列的值。您也可以在 C# 代码中增加应用程序级别的值来实现此目的。
- 生成一个随机整数来为结果带来一些随机性。这可以使用
Random.Next() 和任何种子来完成,甚至是在上一步中生成的数字。
- 使用方法
EncodeInt32AsString 将前两个步骤中的整数转换为两个字符串(一个是唯一字符串,一个是随机字符串)。该方法返回一个字符串,该字符串仅由方法中指定的允许字符组成。此方法的逻辑类似于不同基数之间的数字转换(例如,将允许的字符串更改为仅0-9,或仅0-9A-F以获得十进制/十六进制表示)。因此,结果是由allowedList 中的“数字”组成的“数字”。
-
连接返回的字符串。保持整个唯一字符串原样(以保证唯一性)并从随机字符串中添加尽可能多的字符以将总长度填充到所需长度。如果需要,可以通过在随机点将随机字符串中的字符注入唯一字符串来实现这种连接。
通过保留整个唯一字符串,可以确保最终结果的唯一性。
通过使用随机字符串,这引入了随机性。如果目标字符串的长度非常接近唯一字符串的长度,则无法保证随机性。
在我的测试中,为Int32.MaxValue 调用EncodeInt32AsString 返回一个唯一的6 个字符长的字符串:
2147483647:ZIK0ZJ
在此基础上,目标字符串长度 12 将是理想的,但 10 也是合理的。
EncodeInt32AsString 方法
/// <summary>
/// Encodes the 'input' parameter into a string of characters defined by the allowed list (0-9, A-Z)
/// </summary>
/// <param name="input">Integer that is to be encoded as a string</param>
/// <param name="maxLength">If zero, the string is returned as-is. If non-zero, the string is truncated to this length</param>
/// <returns></returns>
static String EncodeInt32AsString(Int32 input, Int32 maxLength = 0)
{
// List of characters allowed in the target string
Char[] allowedList = new Char[] {
'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' };
Int32 allowedSize = allowedList.Length;
StringBuilder result = new StringBuilder(input.ToString().Length);
Int32 moduloResult;
while (input > 0)
{
moduloResult = input % allowedSize;
input /= allowedSize;
result.Insert(0, allowedList[moduloResult]);
}
if (maxLength > result.Length)
{
result.Insert(0, new String(allowedList[0], maxLength - result.Length));
}
if (maxLength > 0)
return result.ToString().Substring(0, maxLength);
else
return result.ToString();
}
GetRandomizedString 方法
现在,前面的方法只负责对字符串进行编码。为了实现唯一性和随机性属性,可以使用以下逻辑(或类似的)。
在 cmets 中,Kevin 指出了实施 EncodeInt32AsString 方法的以下风险:
需要对代码进行调整,使其返回一个固定长度的字符串。
否则,你永远无法保证最终的结果是独一无二的。
如果有帮助,请想象一个生成 ABCDE(唯一)的值 +
F8CV1(随机)...然后再生成另一个值
ABCDEF(唯一)+ 8CV1(随机)。两个值都是 ABCDEF8CV1
这是一个非常有效的观点,并且已在以下GetRandomizedString 方法中通过指定唯一和随机字符串的长度来解决。 EncodeInt32AsString 方法也被修改为将返回值填充到指定的长度。
// Returns a string that is the encoded representation of the input number, and a random value
static String GetRandomizedString(Int32 input)
{
Int32 uniqueLength = 6; // Length of the unique string (based on the input)
Int32 randomLength = 4; // Length of the random string (based on the RNG)
String uniqueString;
String randomString;
StringBuilder resultString = new StringBuilder(uniqueLength + randomLength);
// This might not be the best way of seeding the RNG, so feel free to replace it with better alternatives.
// Here, the seed is based on the ratio of the current time and the input number. The ratio is flipped
// around (i.e. it is either M/N or N/M) to ensure an integer is returned.
// Casting an expression with Ticks (Long) to Int32 results in truncation, which is fine since this is
// only a seed for an RNG
Random randomizer = new Random(
(Int32)(
DateTime.Now.Ticks + (DateTime.Now.Ticks > input ? DateTime.Now.Ticks / (input + 1) : input / DateTime.Now.Ticks)
)
);
// Get a random number and encode it as a string, limit its length to 'randomLength'
randomString = EncodeInt32AsString(randomizer.Next(1, Int32.MaxValue), randomLength);
// Encode the input number and limit its length to 'uniqueLength'
uniqueString = EncodeInt32AsString(input, uniqueLength);
// For debugging/display purposes alone: show the 2 constituent parts
resultString.AppendFormat("{0}\t {1}\t ", uniqueString, randomString);
// Take successive characters from the unique and random strings and
// alternate them in the output
for (Int32 i = 0; i < Math.Min(uniqueLength, randomLength); i++)
{
resultString.AppendFormat("{0}{1}", uniqueString[i], randomString[i]);
}
resultString.Append((uniqueLength < randomLength ? randomString : uniqueString).Substring(Math.Min(uniqueLength, randomLength)));
return resultString.ToString();
}
样本输出
对各种输入值调用上述方法会导致:
Input Int Unique String Random String Combined String
------------ ----------------- -------------- ---------------------
-10 000000 CRJM 0C0R0J0M00
0 000000 33VT 03030V0T00
1 000001 DEQK 0D0E0Q0K01
2147 0001NN 6IU8 060I0U18NN
21474 000GKI VNOA 0V0N0OGAKI
214748 004LP8 REVP 0R0E4VLPP8
2147483 01A10B RPUM 0R1PAU1M0B
21474836 0CSA38 RNL5 0RCNSLA538
214748364 3JUSWC EP3U 3EJPU3SUWC
2147483647 ZIK0ZJ BM2X ZBIMK20XZJ
1 000001 QTAF 0Q0T0A0F01
2 000002 GTDT 0G0T0D0T02
3 000003 YMEA 0Y0M0E0A03
4 000004 P2EK 0P020E0K04
5 000005 17CT 01070C0T05
6 000006 WH12 0W0H010206
7 000007 SHP0 0S0H0P0007
8 000008 DDNM 0D0D0N0M08
9 000009 192O 0109020O09
10 00000A KOLD 0K0O0L0D0A
11 00000B YUIN 0Y0U0I0N0B
12 00000C D8IO 0D080I0O0C
13 00000D KGB7 0K0G0B070D
14 00000E HROI 0H0R0O0I0E
15 00000F AGBT 0A0G0B0T0F
如上所示,唯一字符串对于序列号是可预测的,因为它只是以不同基数表示的相同数字。但是,随机字符串会带来一些熵,以防止用户猜测后续数字。此外,通过将 唯一字符串 和 随机字符串 的“数字”交错,用户观察任何模式变得稍微困难一些。
在上面的例子中,唯一字符串的长度设置为6(因为它可以表示Int32.MaxValue),但是随机字符串的长度 设置为 4,因为 OP 需要 10 个字符的总长度。