9

在 Java 中,当我们尝试使用正则表达式进行模式匹配时。例如,获取一个输入字符串并使用正则表达式来确定它是否为数字。如果不是,则抛出异常。在这种情况下,我理解,使用正则表达式使代码比我们获取字符串的每个字符,检查它是否是一个数字,如果不是抛出异常更简洁。

但我假设正则表达式也使过程更有效率。这是真的?我找不到关于这一点的任何证据。正则表达式如何在幕后进行匹配?它不是也遍历字符串并逐个检查每个字符吗?

4

8 回答 8

4

只是为了好玩,我运行了这个微基准测试。最后一次运行的结果(即 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;
    }
}
于 2012-08-09T01:25:04.457 回答
3

我还没有技术答案,但我可以写一些代码看看。我不认为正则表达式是将字符串转换为数字的方法。在许多情况下,它们可以更有效,但如果写得不好,它会很慢。

但是我可以问一下,你为什么不只是使用: Integer.parseInt("124")?这将引发 NumberFormatException。应该能够处理它,并且它将对数字的检测留给核心 Java。

于 2012-08-09T01:18:57.960 回答
1

关于幕后的正则表达式......

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

有关更多信息,请参阅FSM并参阅Automata-based_programming

于 2012-08-09T01:43:41.837 回答
1

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.

于 2012-08-09T01:45:18.343 回答
1

只是我的 5 美分 :) 一般来说,正则表达式语言不仅仅用于解析整数或字符串,它是一个非常强大的工具,可以识别任何“正则表达式”。它让我想起了我的大学时光(还记得 Automatas Theory 课程吗?:),但这里的链接描述了常规语言的真正含义

现在由于它构建 FSM 引入了一些开销,所以对于Integer.parseInt正则表达式引擎来说可能不是一个很好的替代品,而且 Java 引入了更具体的 API。然而,正则表达式在处理更复杂的表达式以及我们有很多表达式时有好处。

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

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

于 2012-08-09T05:04:24.157 回答
0

具体回答你的问题:

为什么不对一些复杂的文本应用正则表达式模式匹配,然后尝试自己编写相同的匹配代码。

看看哪个更快。

答:正则表达式。

于 2012-08-09T01:39:12.667 回答
0

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

有一些算法可以实现更快的模式匹配,提供 O(N) 性能,但与 Java 正则表达式相比,功能更少。

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

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

于 2012-08-09T01:24:24.943 回答
0

好吧,很难确定,但在一般情况下,与显式字符检查相比,正则表达式不太可能更有效。RE 是最终状态自动机,因此自动机的构建和维护有一些开销。在我的实践中,显式代码总是比正则表达式更快(因此更有效)。

但这是两难的。从交付时间的角度来看,正则表达式几乎总是更有效,并且在正确使用时更具可读性。这是另一个困境。我很少看到正则表达式的正确用法......

在您的情况下,我建议使用番石榴库:

boolean isValid = DIGIT.matchesAllOf("1234");
于 2012-08-09T01:20:41.587 回答