记一次随机字符串生成算法的随机概率与性能的提升

一、前言背景

前几天我部门一个和银行对接的项目中出现了业务Id重复的现象,导致了很多之前不可预见的bug。由于该项目有资金流动,涉及到金钱交易,故不敢有任何闪失。于是leader把同事写的Handler.ashx.cs发给我瞧了瞧,其中的一处流水号生成代码引起了我的注意。代码如下:

string[] str1 = new string[] { "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" };
string[] str2 = new string[] { "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" };
string[] str3 = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
Random r = new Random();
int s1 = r.Next(0, str1.Length - 1);
string a1 = str1[s1];

r = new Random();
int s2 = r.Next(0, str2.Length - 1);
string a2 = str2[s2];

r = new Random();
int s3 = r.Next(0, str3.Length - 1);
string a3 = str3[s3];

string lsh = a1 + a2 + a3;

由于交易有可能中途失败,所以每次重新交易的时候,订单号是不变的,但是会重新生成流水号。也就是说在交易开始的时候生成固定订单号,到最终交易结束,这一个业务流程有可能一次性成功,有可能重复多次才能成功,毕竟交易有可能第一次就失败,也有可能连续失败好几次。

业务Id=交易订单号+流水号,其中订单号预留9位,流水号规定为3位由大小写英文字母和数字组成的随机字符串。由于订单号是固定且唯一的,那么只要保证生成的流水号是唯一的就能够保证业务Id是唯一的。不过流水号的唯一性的适应范围是依赖订单号的,类似于根据订单号分组,然后每一组里面的流水号不能重复。

于是我又急急忙忙问了维护该项目的同事,按照以往惯例,一笔交易最多失败多少次才能成功,同事说最多不会超过10-12次吧。那么也就是说同一个订单号最多也就生成12个流水号,而由大小写英文字母和阿拉伯数字组成的三位随机字符串最多有238328种。但是再看前面同事的代码可以看出,同事生成的随机字符串组成方式是[ 大写英文字母+小写英文字母+阿拉伯数字 ],而按照这种固定的组合方式,最多只能生成26*26*10=6760种三位随机字符串,显然重复概率偏大。

然后再瞧一瞧上面的代码,可以看出同事是使用与时间相关的默认种子值,初始化 Random 类的新实例。并且实例化了3个Random对象,很显然3个Random对象极有可能会产生一模一样的随机数。默认情况下,Random 类的无参数构造函数使用系统时钟生成其种子值,而参数化构造函数可根据当前时间的计时周期数采用Int32值。 但是,因为时钟的分辨率有限,所以,如果使用无参数构造函数连续创建不同的 Random 对象,就会创建生成相同随机数序列的随机数生成器。

经过了这么一分析,显然这种生成三位随机字符串的方式存在极大的重复隐患。由于博主一贯主张在公司干活的首要目标是快速解决问题,于是博主决定先去网上找一找,看看有没有比较通用靠谱的代码。但是几近波折,发现大多不如意,好吧,抡起袖子自己造轮子吧!

二、技术实现

1、Random 类是伪随机数生成器,伪随机数是以相同的概率从一组有限的数字中选取的。 所选数字并不具有完全的随机性,因为它们是用一种确定的数学算法选择的,但是从实用的角度而言,其随机程度已足够了。但是针对上述场景用起来总觉得随机性偏弱,于是博主在MSDN有了新的发现,发现了一个可以生成强随机数的类:RNGCryptoServiceProvider。该类可以使用加密服务提供程序 (CSP) 提供的实现来实现加密随机数生成器 (RNG),显然随机性要大于Random类。

private int GetRandomInt(int maxValue)
{
    if (maxValue < 0)
    {
        throw new ArgumentOutOfRangeException("maxValue", "maxValue 小于零。");
    }
    S_rng.GetBytes(S_buffer);
    int value = BitConverter.ToInt32(S_buffer, 0);
    value = value % (maxValue + 1);
    if (value < 0) value = -value;
    return value;
}

2、同事既然把生成随机字符串的方式固定为[ 大写英文字母+小写英文字母+阿拉伯数字 ],显然这样子不对,还可以有诸如[ 大写英文字母+大写英文字母+大写英文字母 ]、[ 大写英文字母+阿拉伯数字+大写英文字母 ]等等其他组合方式的。也就是数学里面的组合,从n个不同元素中任意取出m个元素进行组合,允许组合内有重复元素,比如生成4位随机字符串就是[ 大写英文字母+小写英文字母+阿拉伯数字+阿拉伯数字 ]。

为了便于支持多种数据类型的元素进行可重复组合,采用泛型。

private void GetAllCombination<T>(List<T[]> values, T[] array, T[] buffer, int index)
{
    if (index == 0)
    {
        foreach (T value in array)
        {
            buffer[0] = value;
            T[] tmp = new T[buffer.Length];
            buffer.CopyTo(tmp, 0);
            int l = tmp.Length;
            for (int i = 0; i < l / 2; i++)
            {
                T t = tmp[i];
                tmp[i] = tmp[l - i - 1];
                tmp[l - i - 1] = t;
            }
            values.Add(tmp);
        }
    }
    else
    {
        foreach (T value in array)
        {
            buffer[index] = value;
            GetAllCombination(values, array, buffer, index - 1);
        }
    }
}

private List<T[]> GetAllCombination<T>(T[] array, int m)
{
    List<T[]> values = new List<T[]>();
    T[] buffer = new T[m];
    GetAllCombination(values, array, buffer, m - 1);
    return values;
}

3、为了保证RandomString类的同一个实例对象生成的随机字符串都是唯一的,博主特意在内部弄了一个容器,并且加锁以支持多线程访问。

/// <summary>
/// 生成一个由大小写英文字母和数字组成的随机字符串
/// </summary>
/// <returns></returns>
public string Next()
{
    while (true)
    {
        string indexType = CombinationType[GetRandomInt(CombinationType.Count - 1)].Item2;
        string randomString = string.Empty;
        foreach (var item in indexType)
        {
            randomString += GetRandomChar(item.ToString());
        }

        lock (this._lockObj)
        {
            if (!this._listRandomString.Contains(randomString))
            {
                this._listRandomString.Add(randomString);
                return randomString;
            }
        }
    }
}

4、扩展属性

/// <summary>
/// 随机字符串的位置组合信息
/// </summary>
public List<Tuple<int, string, int>> CombinationType { get; private set; }
/// <summary>
/// RandomString对象实例最多可以产生的随机字符串
/// </summary>
public int Count { get; private set; }

5、测试效果

三、结语思考

以上关键代码均以贴出,博主也只是阐述自己的思考方式,借助此文抛砖引玉,希望得到大家指点。由于组合用的是递归算法,则必然导致性能低下,可否有大神还有其他方式进行优化?

通过性能测试可以看出,当m为13时开始出现瓶颈。

时间: 2024-10-07 06:29:17

记一次随机字符串生成算法的随机概率与性能的提升的相关文章

随机字符串生成

function TfrmPWGenerate.btnGenerateClick(Sender: TObject): string; {max length of generated password}const  intMAX_PW_LEN = 10;var  i: Byte;  s: string;begin  {if you want to use the 'A..Z' characters}  if cbAZ.Checked then    s := 'ABCDEFGHIJKLMNOPQ

随机红包生成算法-python实现

抢红包那么开心,那你知道红包随机算法是怎么样的吗? 我模拟写了一个定额随机红包生成算法,如下. 输入: 红包总额,total 份数,num 调控参数(调控红包最平均差,默认为2) 约束: 每份最少有1分钱,即0.01 份数需为正整数 红包总额 <= 份数×0.01 输出 随机红包序列,序列长度等于红包份数 运气王,即红包数额最大的一份 # -*- coding: cp936 -*- # 思路:先随机出来m个数,然后平均分成m个数字只和的份数,然后将钱平均分给m个人# import random

(原创 untiy) 随机地牢生成算法(一)

转载注明出处:http://www.cnblogs.com/m-f-s/p/6509135.html 根据http://www.gamelook.com.cn/2015/12/239245 翻译整理  进行实现: 生成房间 首先,你要生成一些宽和高不同的房间,随机地放在一个圈内.TKdev的算法用了比较常见的方法随机生成房间尺寸,我认为这是一个不错的想法,因为它可以为你带来更多的参数可供使用,使用不同的宽高比例和标准偏差可以带来外观不同的副本地牢. 我采用此函数来返回一个半径为_r圆内的随机点:

纸牌生成算法(随机数组)

NSArray* array = [NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], [NSNumber numberWithInt:3], [NSNumber numberWithInt:4], [NSNumber numberWithInt:5], nil]; NSMutableArray* randomArray = [NSMutableArray arrayWithArray:

随机字符串生成指定范围内的某个数值

应用场景:我根据任意用户id.群id,生成固定一个数值,便于我给该用户.群组给头像. 1 function imgNum(id, num) { 2 let arr = id.split(""), 3 sum = 0; 4 arr.forEach(val => sum += val.charCodeAt()); 5 return sum % num 6 } 7 imgNum("quroqfehfrk-3-4-5whr", 20); 8 //6 9 imgNum(

以均匀随机分布生成任意斜率随机线性分布

本文的目的是利用(0,1)上的均匀分布随机数生成器生成区间为(imin,imax),斜率为slope的任意归一化线性随机数生成器. 借助(0,1)上的均匀随机数生成器,可以通过反函数法生成任意分布的随机数生成器. 对于C++,生成(imin,imax)上随机数生成器的代码为 double uniform_dist(double imin,double imax) { int temp; while((temp=rand())==RAND_MAX){ ; } return temp/RAND_MA

PHP学习笔记:万能随机字符串生成函数(已经封装好)

做验证码用到的,然后就把这个函数封装起来,使用时候要设置2个参数: $str设置里要被采集的字符串,比如: $str='efasfgzsrhftjxjxjhsrth'; 则在函数里面生成的字符串就回从efasfgzsrhftjxjxjhsrth里面随机抓取: $codeLen设置要生成的随机字符串,设置5,则生成5个随机字符串. 原理:随机抓取字符串,对字符串进行拼接 效果: 代码: <?php //mt_rand 获取随机数 mt_rand(min, max); $str="abcdef

四种迷宫生成算法

简介 所谓迷宫生成算法,就是用以生成随机的迷宫的算法 迷宫生成算法是处于这样一个场景: 一个row行,col列的网格地图,一开始默认所有网格四周的墙是封闭的 要求在网格地图边缘,也就是网格的边上打通2面墙 所有网格都至少保证网格周围至少有一堵墙打通 所有网格都能通过打通的墙能形成一条通路 博主已实现RecursiveBacktracking(递归回溯),RecursiveSegmentation(递归分割),随机Prim算法,Kruskal+并查集四种迷宫生成算法,这篇文章主要对这四种算法进行简

【代码笔记】iOS-产生随机字符串

一,代码: - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSLog(@"---产生随机字符串---%@",[self generateTradeNO]); } //产生随机字符串 - (NSString *)generateTradeNO { static int kNumber = 15; NSSt