我写了一个小基准来估计以下性能:
- NOP 方法(了解基线迭代速度);
- OP 提供的原始方法;
- 正则表达式;
- 编译正则表达式;
- @maraca 提供的版本(没有 toLowerCase 和 substring);
- “fastIsHex”版本(基于switch),我添加只是为了好玩。
测试机配置如下:
- JVM:Java(TM) SE 运行时环境(内部版本 1.8.0_101-b13)
- CPU:Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz
这是我对原始测试字符串 "0x123fa" 和 10.000.000 次迭代得到的结果:
Method "NOP" => #10000000 iterations in 9ms
Method "isHexadecimal (OP)" => #10000000 iterations in 300ms
Method "RegExp" => #10000000 iterations in 4270ms
Method "RegExp (Compiled)" => #10000000 iterations in 1025ms
Method "isHexadecimal (maraca)" => #10000000 iterations in 135ms
Method "fastIsHex" => #10000000 iterations in 107ms
正如您所见,即使是 OP 的原始方法也比 RegExp 方法更快(至少在使用 JDK 提供的 RegExp 实现时)。
(供您参考)
基准代码:
public static void main(String[] argv) throws Exception {
//Number of ITERATIONS
final int ITERATIONS = 10000000;
//NOP
benchmark(ITERATIONS,"NOP",() -> nop(longHexText));
//isHexadecimal
benchmark(ITERATIONS,"isHexadecimal (OP)",() -> isHexadecimal(longHexText));
//Un-compiled regexp
benchmark(ITERATIONS,"RegExp",() -> longHexText.matches("0x[0-9a-fA-F]+"));
//Pre-compiled regexp
final Pattern pattern = Pattern.compile("0x[0-9a-fA-F]+");
benchmark(ITERATIONS,"RegExp (Compiled)", () -> {
pattern.matcher(longHexText).matches();
});
//isHexadecimal (maraca)
benchmark(ITERATIONS,"isHexadecimal (maraca)",() -> isHexadecimalMaraca(longHexText));
//FastIsHex
benchmark(ITERATIONS,"fastIsHex",() -> fastIsHex(longHexText));
}
public static void benchmark(int iterations,String name,Runnable block) {
//Start Time
long stime = System.currentTimeMillis();
//Benchmark
for(int i = 0; i < iterations; i++) {
block.run();
}
//Done
System.out.println(
String.format("Method \"%s\" => #%d iterations in %dms",name,iterations,(System.currentTimeMillis()-stime))
);
}
NOP 方法:
public static boolean nop(String value) { return true; }
fastIsHex 方法:
public static boolean fastIsHex(String value) {
//Value must be at least 4 characters long (0x00)
if(value.length() < 4) {
return false;
}
//Compute where the data starts
int start = ((value.charAt(0) == '-') ? 1 : 0) + 2;
//Check prefix
if(value.charAt(start-2) != '0' || value.charAt(start-1) != 'x') {
return false;
}
//Verify data
for(int i = start; i < value.length(); i++) {
switch(value.charAt(i)) {
case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':
case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':
continue;
default:
return false;
}
}
return true;
}
所以,答案是否定的,对于短字符串和手头的任务,RegExp 并不快。
当谈到更长的字符串时,平衡是完全不同的,
以下是我生成的 8192 长十六进制字符串的结果:
hexdump -n 8196 -v -e '/1 "%02X"' /dev/urandom
和 10.000 次迭代:
Method "NOP" => #10000 iterations in 2ms
Method "isHexadecimal (OP)" => #10000 iterations in 1512ms
Method "RegExp" => #10000 iterations in 1303ms
Method "RegExp (Compiled)" => #10000 iterations in 1263ms
Method "isHexadecimal (maraca)" => #10000 iterations in 553ms
Method "fastIsHex" => #10000 iterations in 530ms
如您所见,手写方法(由 macara 和我的 fastIsHex 编写的方法)仍然胜过 RegExp,但原始方法没有,
(由于 substring() 和 toLowerCase())。
旁注:
这个基准测试确实非常简单,只测试“最坏情况”场景(即完全有效的字符串),现实生活中的结果,混合数据长度和非 0 有效 - 无效比率,可能完全不同.
更新:
我也尝试了char[]数组版本:
char[] chars = value.toCharArray();
for (idx += 2; idx < chars.length; idx++) { ... }
它甚至比 getCharAt(i) 版本慢了一点:
Method "isHexadecimal (maraca) char[] array version" => #10000000 iterations in 194ms
Method "fastIsHex, char[] array version" => #10000000 iterations in 164ms
我的猜测是由于 toCharArray 中的数组复制。
更新(#2):
我已经运行了额外的 8k/100.000 次迭代测试,以查看“maraca”和“fastIsHex”方法之间的速度是否存在任何真正的差异,并且还对它们进行了规范化以使用完全相同的前置条件代码:
运行#1
Method "isHexadecimal (maraca) *normalized" => #100000 iterations in 5341ms
Method "fastIsHex" => #100000 iterations in 5313ms
运行 #2
Method "isHexadecimal (maraca) *normalized" => #100000 iterations in 5313ms
Method "fastIsHex" => #100000 iterations in 5334ms
即这两种方法之间的速度差异充其量是微不足道的,并且可能是由于测量错误(因为我在我的工作站上运行它而不是专门设置的干净测试环境)。