在 Java 中,当我们尝试使用正则表达式进行模式匹配时。例如,获取一个输入字符串并使用正则表达式来确定它是否为数字。如果不是,则抛出异常。在这种情况下,我理解,使用正则表达式使代码比我们获取字符串的每个字符,检查它是否是一个数字,如果不是抛出异常更简洁。
但我假设正则表达式也使过程更有效率。这是真的?我找不到关于这一点的任何证据。正则表达式如何在幕后进行匹配?它不是也遍历字符串并逐个检查每个字符吗?
在 Java 中,当我们尝试使用正则表达式进行模式匹配时。例如,获取一个输入字符串并使用正则表达式来确定它是否为数字。如果不是,则抛出异常。在这种情况下,我理解,使用正则表达式使代码比我们获取字符串的每个字符,检查它是否是一个数字,如果不是抛出异常更简洁。
但我假设正则表达式也使过程更有效率。这是真的?我找不到关于这一点的任何证据。正则表达式如何在幕后进行匹配?它不是也遍历字符串并逐个检查每个字符吗?
只是为了好玩,我运行了这个微基准测试。最后一次运行的结果(即 JVM 预热 / JIT)如下(无论如何,从一次运行到另一次运行的结果相当一致):
regex with numbers 123
chars with numbers 33
parseInt with numbers 33
regex with words 123
chars with words 34
parseInt with words 733
换句话说,chars 非常有效,如果字符串是数字,则 Integer.parseInt 与 char 一样有效,但如果字符串不是数字,则非常慢。正则表达式介于两者之间。
结论
如果您将字符串解析为数字并且您希望字符串通常是数字,则使用 Integer.parseInt 是最佳解决方案(高效且可读)。如果字符串不是太频繁,则字符串不是数字时得到的惩罚应该很低。
ps:我的正则表达式可能不是最佳的,请随时发表评论。
public class TestNumber {
private final static List<String> numbers = new ArrayList<>();
private final static List<String> words = new ArrayList<>();
public static void main(String args[]) {
long start, end;
Random random = new Random();
for (int i = 0; i < 1000000; i++) {
numbers.add(String.valueOf(i));
words.add(String.valueOf(i) + "x");
}
for (int i = 0; i < 5; i++) {
start = System.nanoTime();
regex(numbers);
System.out.println("regex with numbers " + (System.nanoTime() - start) / 1000000);
start = System.nanoTime();
chars(numbers);
System.out.println("chars with numbers " + (System.nanoTime() - start) / 1000000);
start = System.nanoTime();
exception(numbers);
System.out.println("exceptions with numbers " + (System.nanoTime() - start) / 1000000);
start = System.nanoTime();
regex(words);
System.out.println("regex with words " + (System.nanoTime() - start) / 1000000);
start = System.nanoTime();
chars(words);
System.out.println("chars with words " + (System.nanoTime() - start) / 1000000);
start = System.nanoTime();
exception(words);
System.out.println("exceptions with words " + (System.nanoTime() - start) / 1000000);
}
}
private static int regex(List<String> list) {
int sum = 0;
Pattern p = Pattern.compile("[0-9]+");
for (String s : list) {
sum += (p.matcher(s).matches() ? 1 : 0);
}
return sum;
}
private static int chars(List<String> list) {
int sum = 0;
for (String s : list) {
boolean isNumber = true;
for (char c : s.toCharArray()) {
if (c < '0' || c > '9') {
isNumber = false;
break;
}
}
if (isNumber) {
sum++;
}
}
return sum;
}
private static int exception(List<String> list) {
int sum = 0;
for (String s : list) {
try {
Integer.parseInt(s);
sum++;
} catch (NumberFormatException e) {
}
}
return sum;
}
}
我还没有技术答案,但我可以写一些代码看看。我不认为正则表达式是将字符串转换为数字的方法。在许多情况下,它们可以更有效,但如果写得不好,它会很慢。
但是我可以问一下,你为什么不只是使用:
Integer.parseInt("124")
?这将引发 NumberFormatException。应该能够处理它,并且它将对数字的检测留给核心 Java。
关于幕后的正则表达式......
有限状态机 (FSM)等价于正则表达式。FSM 是一台可以识别语言的机器(在您的情况下为数字)。FSM 具有字母表、状态、初始状态、N 个最终状态以及从一种状态到另一种状态的转换函数。字符串需要包含在字母表中(例如 ASCII)。FSM 从初始状态开始。当您输入一个字符串时,它会根据函数(状态,字符)=>状态从一个状态移动到另一个状态,逐个处理字符。当它达到最终状态时,您知道您的字符串是否为数字。
有关更多信息,请参阅FSM并参阅Automata-based_programming
I don't see how it could get any simpler or easier to read than:
Integer.parseInt()
or
Double.parseDouble()
They do exactly what you describe including throw an Exception for invalid input.
Regarding performance: I would expect a regex to be less efficient than the above.
只是我的 5 美分 :) 一般来说,正则表达式语言不仅仅用于解析整数或字符串,它是一个非常强大的工具,可以识别任何“正则表达式”。它让我想起了我的大学时光(还记得 Automatas Theory 课程吗?:),但这里的链接描述了常规语言的真正含义
现在由于它构建 FSM 引入了一些开销,所以对于Integer.parseInt
正则表达式引擎来说可能不是一个很好的替代品,而且 Java 引入了更具体的 API。然而,正则表达式在处理更复杂的表达式以及我们有很多表达式时有好处。
必须明智地使用正则表达式。必须始终编译模式(否则不能有效地重用,因为每次编译模式都会消耗性能)
我建议在更复杂的输入上运行测试,看看会发生什么。
具体回答你的问题:
为什么不对一些复杂的文本应用正则表达式模式匹配,然后尝试自己编写相同的匹配代码。
看看哪个更快。
答:正则表达式。
最后,它确实在遍历字符串并检查每个字符,试图为提供的模式找到匹配项。此外,它使用回溯(如果有很多可能匹配的方法,引擎会尝试所有方法),这可能会导致某些不寻常情况下的性能非常差(您不太可能遇到这种情况,但理论上可能)。在最坏情况下,java 正则表达式引擎的性能是 O(2 N ),其中 N 是输入字符串的长度。
有一些算法可以实现更快的模式匹配,提供 O(N) 性能,但与 Java 正则表达式相比,功能更少。
这是一篇详细讨论这个问题的文章。
但在大多数情况下,正则表达式引擎不会成为您应用程序的性能瓶颈。它足够快,所以一般不用担心它,除非你的分析器指向它。它提供了算法的声明性描述,这非常有用,因为几乎总是迭代算法实现会更冗长且可读性更低。
好吧,很难确定,但在一般情况下,与显式字符检查相比,正则表达式不太可能更有效。RE 是最终状态自动机,因此自动机的构建和维护有一些开销。在我的实践中,显式代码总是比正则表达式更快(因此更有效)。
但这是两难的。从交付时间的角度来看,正则表达式几乎总是更有效,并且在正确使用时更具可读性。这是另一个困境。我很少看到正则表达式的正确用法......
在您的情况下,我建议使用番石榴库:
boolean isValid = DIGIT.matchesAllOf("1234");