1. 概述
非捕获组是Java 正则表达式中的重要构造。 他们创建一个子模式,该子模式充当单个单元,但不保存匹配的字符序列。 在本教程中,我们将探讨如何在 Java 正则表达式中使用非捕获组。
2. 正则表达式组
正则表达式组可以是以下两种类型之一:捕获型和非捕获型。
捕获组保存匹配的字符序列。它们的值可以用作模式中的反向引用和/或稍后在代码中检索。
尽管它们不保存匹配的字符序列, 但非捕获组可以更改组内的模式匹配修饰符。一些非捕获组甚至可以在成功的子模式匹配后丢弃回溯信息。
让我们探讨一些非捕获组的实际例子。
3. 非捕获组
使用运算符“ (?:X) ”创建非捕获组。 “ X ”是该组的模式:
Pattern.compile("[^:]+://(?:[.a-z]+/?)+")
该模式有一个非捕获组。如果它是类似 URL 的,它将匹配一个值。 URL 的完整正则表达式会涉及更多内容。我们使用一个简单的模式来关注非捕获组。
模式 “[^:]: ”与协议匹配,例如“ http:// ”。非捕获组“ (?:[.az]+/?) ”与带有可选斜杠的域名相匹配。由于“ + ”运算符匹配此模式的一次或多次出现,因此我们也将匹配后续路径段。让我们在 URL 上测试这个模式:
Pattern simpleUrlPattern = Pattern.compile("[^:]+://(?:[.a-z]+/?)+");
Matcher urlMatcher
= simpleUrlPattern.matcher("http://www.microsoft.com/some/other/url/path");
Assertions.assertThat(urlMatcher.matches()).isTrue();
让我们看看当我们尝试检索匹配的文本时会发生什么:
Pattern simpleUrlPattern = Pattern.compile("[^:]+://(?:[.a-z]+/?)+");
Matcher urlMatcher = simpleUrlPattern.matcher("http://www.microsoft.com/");
Assertions.assertThat(urlMatcher.matches()).isTrue();
Assertions.assertThatThrownBy(() -> urlMatcher.group(1))
.isInstanceOf(IndexOutOfBoundsException.class);
正则表达式被编译成 java.util.Pattern 对象。然后,我们创建一个 java.util.Matcher 将我们的 模式 应用于提供的值。
接下来,我们断言 matches() 的结果返回 true 。
我们使用非捕获组来匹配 URL 中的域名。 * 由于非捕获组不保存匹配文本,因此我们无法检索匹配文本 “ www.microsoft.com/” 。 * 尝试检索域名将导致 IndexOutOfBoundsException 。
3.1.内联修饰符
正则表达式区分大小写。 如果我们将模式应用于大小写混合的 URL,匹配将会失败:
Pattern simpleUrlPattern
= Pattern.compile("[^:]+://(?:[.a-z]+/?)+");
Matcher urlMatcher
= simpleUrlPattern.matcher("http://www.Microsoft.com/");
Assertions.assertThat(urlMatcher.matches()).isFalse();
如果我们也想匹配大写字母,我们可以尝试一些选项。
一种选择是将大写字符范围添加到模式中:
Pattern.compile("[^:]+://(?:[.a-zA-Z]+/?)+")
另一种选择是使用修饰符标志。因此,我们可以将正则表达式编译为不区分大小写:
Pattern.compile("[^:]+://(?:[.a-z]+/?)+", Pattern.CASE_INSENSITIVE)
非捕获组允许第三种选择: 我们可以仅更改组的修饰符标志。 让我们将不区分大小写的修饰符标志(“ i ”)添加到组中:
Pattern.compile("[^:]+://(?i:[.az]+/?)+");
现在我们已经使组不区分大小写,让我们将此模式应用于大小写混合的 URL:
Pattern scopedCaseInsensitiveUrlPattern
= Pattern.compile("[^:]+://(?i:[.a-z]+/?)+");
Matcher urlMatcher
= scopedCaseInsensitiveUrlPattern.matcher("http://www.Microsoft.com/");
Assertions.assertThat(urlMatcher.matches()).isTrue();
当模式被编译为区分大小写时,我们可以通过在修饰符前面添加“-”运算符来关闭它。 让我们将此模式应用到另一个混合大小写的 URL:
Pattern scopedCaseSensitiveUrlPattern
= Pattern.compile("[^:]+://(?-i:[.a-z]+/?)+/ending-path", Pattern.CASE_INSENSITIVE);
Matcher urlMatcher
= scopedCaseSensitiveUrlPattern.matcher("http://www.Microsoft.com/ending-path");
Assertions.assertThat(urlMatcher.matches()).isFalse();
在此示例中,最终路径段“ /ending-path ”不区分大小写。模式的“ /ending-path ”部分将匹配大写和小写字符。
当我们关闭组内的不区分大小写选项时,非捕获组仅支持小写字符。因此,大小写混合域名不匹配。
4. 独立的非捕获组
独立非捕获组是一种正则表达式组。这些组 在找到成功匹配后丢弃回溯信息 。当使用这种类型的组时,我们需要注意何时会发生回溯。否则,我们的模式可能与我们认为应该的价值观不匹配。
回溯是非确定性有限自动机 (NFA) 正则表达式引擎的一项功能。 当引擎无法匹配文本时,NFA 引擎可以探索模式中的替代方案。 在用尽所有可用的替代方案后,引擎将导致比赛失败。我们只讨论回溯,因为它涉及独立的非捕获组。
使用运算符“ (?>X) ”创建一个独立的非捕获组,其中 X 是子模式:
Pattern.compile("[^:]+://(?>[.az]+/?)+/ending-path");
我们添加了“ /ending-path ”作为常量路径段。有了这个额外的要求,就会迫使出现回溯的情况。域名和其他路径段可以匹配斜杠字符。为了匹配 “/ending-path” ,引擎需要回溯。通过回溯,引擎可以从组中删除斜杠并将其应用于模式的“ /ending-path ”部分。
让我们将独立的非捕获组模式应用到 URL:
Pattern independentUrlPattern
= Pattern.compile("[^:]+://(?>[.a-z]+/?)+/ending-path");
Matcher independentMatcher
= independentUrlPattern.matcher("http://www.microsoft.com/ending-path");
Assertions.assertThat(independentMatcher.matches()).isFalse();
该组与域名和斜杠匹配成功。因此,我们保留了独立非捕获组的范围。
此模式要求在 “ending-path” 之前出现斜杠。然而,我们的独立非捕获组已经匹配了斜线。
NFA 引擎应尝试回溯。 由于斜杠在组末尾是可选的,因此 NFA 引擎将从组中删除斜杠并重试。 独立非捕获组丢弃了回溯信息。因此,NFA 引擎无法回溯。
4.1.集团内部回溯
回溯可以发生在独立的非捕获组内。 当NFA引擎匹配该组时,回溯信息并未被丢弃。直到组匹配成功后,回溯信息才会被丢弃:
Pattern independentUrlPatternWithBacktracking
= Pattern.compile("[^:]+://(?>(?:[.a-z]+/?)+/)ending-path");
Matcher independentMatcher
= independentUrlPatternWithBacktracking.matcher("http://www.microsoft.com/ending-path");
Assertions.assertThat(independentMatcher.matches()).isTrue();
现在我们在一个独立的非捕获组中有一个非捕获组。我们仍然存在回溯情况,涉及 “ending-path” 前面的斜杠。然而,我们已将模式的回溯部分包含在独立的非捕获组内。 回溯将发生在独立的非捕获组内。因此,NFA 引擎有足够的信息进行回溯,并且模式与提供的 URL 相匹配。
5. 结论
我们已经证明非捕获组与捕获组不同。然而,它们就像它们的捕获对应物一样作为一个单元发挥作用。我们还表明, 非捕获组可以启用或禁用该组的修饰符,而不是整个模式 。
同样,我们已经展示了独立的非捕获组如何丢弃回溯信息。 如果没有此信息,NFA 引擎就无法探索替代方案来进行成功匹配。 然而,团体内部可能会发生回溯。
与往常一样,源代码可以在 GitHub 上获取。