关于正则表达式:Java正则表达式提供了什么性能优势?

Java regular expression offers any performance benefit?

在Java中,当我们尝试使用正则表达式进行模式匹配时。例如,取一个输入字符串并使用正则表达式来确定它是否是数字。如果不是,则抛出异常。在本例中,我了解到,使用regex使代码的详细程度比我们获取字符串的每个字符时要低,检查它是否是数字,是否引发异常。

但我的假设是,regex还可以提高流程的效率。这是真的吗?在这一点上我找不到任何证据。雷杰克斯在幕后的表现如何?它不是也迭代字符串并逐个检查每个字符吗?


为了好玩,我运行了这个微基准测试。最后一次运行的结果(即,JVM预热/JIT之后)如下(无论如何,从一次运行到另一次运行的结果都相当一致):

1
2
3
4
5
6
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相同(如果字符串是数字),但如果字符串不是数字,则速度非常慢。Regex介于两者之间。

结论

如果您将一个字符串解析为一个数字,并且希望该字符串通常是一个数字,那么使用integer.parseint是最佳解决方案(高效且可读)。当字符串不是数字时得到的惩罚如果不是太频繁,应该很低。

附言:我的正则表达式可能不是最佳的,请随意评论。

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
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")?这将引发一个数字格式异常。应该能够处理它,它会检测到核心Java的数量。


只是我的5美分:)一般来说,正则表达式语言并不是只解析整数或字符串,它是一个非常强大的工具,可以识别任何"正则表达式"。它提醒了我上大学的时间(还记得自动机理论课程吗?:),但是这里有一个链接描述了常规语言的真正含义。

既然它建立了FSM,它引入了一些开销,所以对于EDCOX1,2正则表达式引擎不是一个很好的替代,而且Java引入了更具体的API。然而,正则表达式在处理更复杂的表达式时以及在我们有很多表达式时都有好处。

必须明智地使用正则表达式。必须始终编译模式(否则无法有效地重用它,因为每次编译模式都会耗尽性能)

我建议在更复杂的输入上运行测试,看看会发生什么。


我不明白它是如何变得更简单或更容易阅读的:

Integer.parseInt()

Double.parseDouble()

它们执行您所描述的操作,包括为无效输入抛出异常。

关于性能:我希望regex的效率比上面提到的要低。


关于Regex幕后…

有限状态机(FSM)等价于正则表达式。FSM是一种可以识别语言(在您的案例编号中)的机器。FSM具有字母表、状态、初始状态、N-最终状态以及从一个状态到另一个状态的转换函数。字符串需要包含在字母表中(例如ASCII)。FSM从初始状态开始。当您输入一个字符串时,它根据函数(state,char)=>状态逐字符处理从一个状态移动到另一个状态的过程。当它达到最终状态时,您知道字符串是否是数字。

有关更多信息,请参阅FSM和基于自动机的编程


具体回答问题:

为什么不在一些复杂的文本上应用regex模式匹配,然后尝试自己编写相同的匹配代码呢?

看看哪个更快。

答案:正则表达式。


最后,它确实在对字符串进行迭代,并检查每个字符,试图找到所提供模式的匹配项。此外,它还使用回溯(如果有许多可能匹配的方法,引擎将尝试所有方法),这可能会导致某些异常情况下的性能非常差(不太可能遇到这种情况,但理论上是可能的)。在最坏的情况下,Java正则表达式引擎的性能是O(2n),其中n是输入字符串的长度。

有更快的模式匹配算法提供O(N)性能,但与Java正则表达式相比具有更少的特征。

这是一篇详细讨论这个问题的文章。

但在大多数情况下,正则表达式引擎不会成为应用程序的性能瓶颈。它足够快,所以通常不要担心,除非您的分析器指向它。它还提供了对算法的声明性描述,这非常有用,因为几乎总是迭代的算法实现会更加冗长,可读性也会大大降低。


好吧,很难说是肯定的,但是在一般情况下,正则表达式比显式字符检查更有效率。Re是最终状态自动机,因此在自动机构建和维护方面存在一些开销。在我的实践中,显式代码总是比正则表达式更快(因而更有效)。

但这是一个两难的问题。正则表达式在传递观点时几乎总是更有效,并且在正确使用时更可读。这是另一个困境。我很少看到正则表达式的正确用法…

在您的场景中,我建议使用guava库:

1
boolean isValid = DIGIT.matchesAllOf("1234");