记一次随机验证码生成的优化过程

总结

拼接字符串最好不要直接用 “+” ,因为编译器遇到 “+” 的时候,会 new 一个 StringBuilder 出来,接着调用 append 方法进行追加,再调用 toString 方法,生成新字符串。在每次循环中,都会创建一个 StringBuilder 对象,且都会调用一次 toString 方法,可见用 “+” 拼接字符串的成本是十分高的。

PS:执行一次字符串 “str += a”,相当于 “str = new StringBuilder(str).append(“a”).toString()”

String、StringBuilder、StringBuffer 的区别

String 为字符串常量,而 StringBuilder 和 StringBuffer 均为字符串变量,即 String 对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

运行效率:StringBuilder > StringBuffer > String

StringBuffer 和 StringBuilder 都继承自 AbstractStringBuilder,原理一样,就是在底层维护了一个 char 数组,唯一的区别只是 StringBuffer 是线程安全的,它对所有方法都做了同步,而 StringBuilder 是非线程安全的。

1
2
3
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}

每次 append 的时候就会往 char[ ] 中填入字符,在最终调用 sb.toString( ) 的时候,用一个 new String( ) 方法把 char 数组里面的内容都转成 String,这样,整个过程中只产生了一个 StringBuilder 对象与一个 String 对象,非常节省空间。

StringBuilde r唯一的性能损耗点在于 char[ ] 容量不够的时候需要进行扩容,扩容需要进行数组拷贝,一定程度上降低了效率。

考虑到扩容的时候有性能上的损耗,尽量在初始化时预估长度并传入该长度,可以避免扩容的发生。

代码

第一次直接写的,感觉就是 foolish code……..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String getCaptcha(int length) {
String rnd = "";
Random random = new Random();
String[] str = {"A", "B", "C", "D", "E", "F", "G", "H", "J", "K",
"L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
"k", "m", "n", "p", "s", "t", "u", "v", "w", "x", "y", "z",
"1", "2", "3", "4", "5", "6", "7", "8", "9"};
for (int i = 0; i < length; i++) {
String rand = str[random.nextInt(str.length)];
rnd += rand;
}
return rnd;
}
}

第二次,想到最近看的 HashMap 源码中将模运算转为与运算,再改成用 ASCII 码计算验证码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String getRandom(int length) {
String rnd = "";
Random random = new Random();
for (int i = 0; i < length; i++) {
// random.nextInt(2) & 1 = random.nextInt(2) % 2
if ((random.nextInt(2) & 1) == 0) {
int temp = (random.nextInt(2) & 1) == 0 ? 65 : 97;
int ascii = random.nextInt(26);
rnd += (char) (ascii + temp);
} else {
rnd += String.valueOf(random.nextInt(10));
}
}
return rnd;
}

第三次,考虑到《Effective Java》中指出,“不要使用字符串连接操作来合并多个字符串,除非性能无非紧要,应该使用 StringBuilder 的 append 方法。”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String getRandom(int length) {
StringBuilder sb = new StringBuilder(length);
Random random = new Random();
while (length--!=0) {
// random.nextInt(2) & 1 = random.nextInt(2) % 2
if ((random.nextInt(2) & 1) == 0) {
int temp = (random.nextInt(2) & 1) == 0 ? 65 : 97;
int ascii = random.nextInt(26);
sb.append((char) (ascii + temp));
} else {
sb.append(String.valueOf(random.nextInt(10)));
}
}
return sb.toString();
}

或者直接使用已有的工具类RandomStringUtils

1
2
3
public String getRandom(int length) {
return RandomStringUtils.randomAlphanumeric(4);
}

RandomStringUtils 中的实现

org.apache.commons.lang.RandomStringUtils 中的 RandomStringUtils 工具类可以实现生成随机字符串,其中生成字母和数字的随机组合的方法是:

public static String randomAlphanumeric(int count)

Creates a random string whose length is the number of characters specified.
Characters will be chosen from the set of alpha-numeric characters.

来看看它的源码具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
*
* public static String randomAlphanumeric(int count) {
* return random(count, true, true);
* }
*
* public static String random(int count, boolean letters, boolean numbers) {
* return random(count, 0, 0, letters, numbers);
* }
*
* public static String random(int count, int start, int end, boolean letters, boolean numbers) {
* return random(count, start, end, letters, numbers, null, RANDOM);
* }
*
* 所以最后调用的是random(count, 0, 0, true, true, null, RANDOM)
* 如果start与end=0,start会设为' ',end会设为'z'即所有可打印的ASCII码都会被用到
*/
public static String random(int count, int start, int end, boolean letters, boolean numbers, char[] chars, Random random) {
if (count == 0) {
return "";
} else if (count < 0) {
throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
}
if ((start == 0) && (end == 0)) {
// 122 + 1
end = 'z' + 1;
// 32
start = ' ';
if (!letters && !numbers) {
start = 0;
end = Integer.MAX_VALUE;
}
}

// 用一个指定长度char数组存放生成的随机字符串
char[] buffer = new char[count];
// 91
int gap = end - start;

while (count-- != 0) {
char ch;
// 传入的chars=null
if (chars == null) {
// 这个循环生成的字符为ASCII码的32-123之间
ch = (char) (random.nextInt(gap) + start);
} else {
ch = chars[random.nextInt(gap) + start];
}
// 如果产生的字符非数字也非字母,则不会存入char数组中
if ((letters && Character.isLetter(ch))
|| (numbers && Character.isDigit(ch))
|| (!letters && !numbers))
{
if(ch >= 56320 && ch <= 57343) {
if(count == 0) {
count++;
} else {
// low surrogate, insert high surrogate after putting it in
buffer[count] = ch;
count--;
buffer[count] = (char) (55296 + random.nextInt(128));
}
} else if(ch >= 55296 && ch <= 56191) {
if(count == 0) {
count++;
} else {
// high surrogate, insert low surrogate before putting it in
buffer[count] = (char) (56320 + random.nextInt(128));
count--;
buffer[count] = ch;
}
} else if(ch >= 56192 && ch <= 56319) {
// private high surrogate, no effing clue, so skip it
count++;
} else {
buffer[count] = ch;
}
// count++,重复当前循环
} else {
count++;
}
}
return new String(buffer);
}
  • 本文作者: Marticles
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!