需求是需要给数据库mysql插入100万个10位的随机字符串,以后随时从数据库里获取100-1000个值用于业务系统。
10位随机字符串可以使用的字符包括0-9和A-Z,任何语言都基本上有随机函数,生成10位随机字符串很简单,但是怎么确保100万条数据都不一样。
效率最低但是最简单的想法是插入一条前和现有的所有数据比较,不一样就插入,发现一样就重新生成一个随机值再比较,时间复杂度是o(n!),很显然对于100万条数据来说这种算法是不可取的。
我们换一个思路,我先顺序生成100万条数据,然后随机从100万条数据里取100-1000条,这样生成数据的时间复杂度是o(n),效率高很多,取随机数据直接使用mysql带的rand()函数。以下是细节:
1. 生成数据
顺序生成100万条数据的方法就是我们最简单的排列组合,可选的字符是36个,字符串10位,则组合可能是 P(36, 10) ,如果我们用笔和纸的话,很容易写出第一个到n个的数据:
0000000000
0000000001
...
000000000A
000000000B
...
000000000Z
0000000010
0000000011
...
其本质其实是一个36进制的数一直在增值1,就是从最后一位开始,一直加,到9加1就到 A ,到 Z 之后再加1就开始进一位。对应的算法代码如下:
//36进制加一进位
private char[] systemAdd(char[] ss) {
int LENGTH = 10;
for (int i = LENGTH - 1; i >= 0; i--) {
int temp = ss[i];
int number9 = '9';
int numberZ = 'Z';
if (temp < number9 - 1) {
ss[i] = (char) ((int) ss[i] + 1);
break;
} else if (temp == number9) {
ss[i] = 'A';
break;
} else if (temp < numberZ) {
ss[i] = (char) ((int) ss[i] + 1);
break;
} else {
ss[i] = '0';
}
}
return ss;
}
这里有一个细节就是 P(36,4)=36*35*34*33 的值就已经大于100万了。我们如果按顺序生成,只会用到后4位,前面6位全是0,为了避免最后生成的随机字符串前6位都一样,我们可以把前6位变成随机值。
//100万前6位都是0,所以把前6位改成随机值
for (int j = 0; j < 6; j++) {
int temp = ThreadLocalRandom.current().nextInt(36);
ss[j] = all[temp];
}
2. 插入数据
我们肯定不能生成一条数据就插一次数据库,100万条数据分10组,每组10万条,我们也不能10万条数据就生成10万个 insert 语句,我们拼成一个 insert 语句,类似:
insert into tablename (cloumn1, column2) values
(c1_v1,c2_v1),
(c1_v2,c2_v2),
(c1_v3,c2_v3)
然后通过 JDBC 执行 SQL 语句完成插入。基本的代码如下:
void run() throws SQLException, ClassNotFoundException {
long index = 0;
long TOTAL = 1000000;
//分组
long PART = 100000;
//分10个insert语句插入数据库
for (long i = 0; i < TOTAL / PART; i++) {
StringBuffer sb = new StringBuffer();
dbService.initInsertSQL(sb);
for (long j = i * PART; j < (i + 1) * PART; j++) {
index++;
if (index > TOTAL) break;
dbService.appendInsertSQL(index, String.valueOf(current), sb);
current = systemAdd(current);
}
sb.deleteCharAt(sb.length() - 1);
dbService.excute(sb.toString());
}
}
最后测试一下,从生成到插入数据库100万条大概8秒,速度是可以接收的。
3. 获取数据
从数据库里获取数据不需要再单独写算法了,利用mysql的rand函数就可以。
SELECT * FROM random_values order by rand() limit 1000
但是要注意的是,获取完需要把这1000条数据从表中删除或加一个标记表示已用,否则下次再获取可能会重复。
完整源码请参考 git