JAVA正则表达式之贪婪、勉强和侵占

  在JAVA正则表达式中量词(quantifiers)允许指定匹配出现的次数,方便起见,当前 Pattern API 规范下,描述了贪婪、勉强和侵占三种量词。首先粗略地看一下,量词X?X??X?+都允许匹配 X 零次或一次,精确地做同样的事情,但它们之间有着细微的不同之处。

量 词 种 类 意  义
贪婪 勉强 侵占
X? X?? X?+ 匹配 X 零次或一次
X* X*? X*+ 匹配 X 零次或多次
X+ X+? X++ 匹配 X 一次或多次
X{n} X{n}? X{n}+ 匹配 X n 次
X{n,} X{n,}? X{n,}+ 匹配 X 至少 n 次
X{n,m} X{n,m}? X{n,m}+ 匹配 X 至少 n 次,但不多于 m 次

  开始之前准备一段可以重复测试的代码:

import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexDemo {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (true) {
			System.out.print("\nRegex:");
			Pattern pattern = Pattern.compile(sc.nextLine());
			System.out.print("String to search: ");
			Matcher matcher = pattern.matcher(sc.nextLine());
			boolean found = false;
			while (matcher.find()) {
				System.out.println("Found the text \"" + matcher.group()
						+ "\" starting at index " + matcher.start()
						+ " and ending at index " + matcher.end() + ".");
				found = true;
			}
			if (!found) {
				System.out.println("No match found.");
			}
		}
	}
}

  从贪婪量词开始,构建三个不同的正则表达式:字母a后面跟着?*+。接下来看一下,用这些表达式来测试输入的字符串是空字符串时会发生些什么:

Regex:a?
String to search:
Found the text "" starting at index 0 and ending at index 0.

Regex:a*String to search: Found the text "" starting at index 0 and ending at index 0.

Regex:a+String to search: No match found.
  • 零长度匹配

  在上面的例子中,开始的两个匹配是成功的,这是因为表达式a?a*都允许字符出现零次。就目前而言,这个例子不像其他的,也许你注意到了开始和结束的索引都是 0。输入的空字符串没有长度,因此该测试简单地在索引 0 上匹配什么都没有,诸如此类的匹配称之为零长度匹配(zero-length-matches)。零长度匹配会出现在以下几种情况:输入空的字符串、在输入字符串的开始处、在输入字符串最后字符的后面,或者是输入字符串中任意两个字符之间。由于它们开始和结束的位置有着相同的索引,因此零长度匹配是容易被发现的。
  我们来看一下关于零长度匹配更多的例子。把输入的字符串改为单个字符“a”,你会注意到一些有意思的事情:

Regex:a?
String to search: a
Found the text "a" starting at index 0 and ending at index 1.
Found the text "" starting at index 1 and ending at index 1.

Regex:a*
String to search: a
Found the text "a" starting at index 0 and ending at index 1.
Found the text "" starting at index 1 and ending at index 1.

Regex:a+
String to search: a
Found the text "a" starting at index 0 and ending at index 1.

  所有的三个量词都是用来寻找字母“a”的,但是前面两个在索引 1 处找到了零长度匹配,也就是说,在输入字符串最后一个字符的后面。回想一下,匹配把字符“a”看作是位于索引 0 和索引 1 之间的单元格中,并且测试用具一直循环下去直到不再有匹配为止。依赖于所使用的量词不同,最后字符后面的索引“什么也没有”的存在可以或者不可以触发一个匹配。
  现在把输入的字符串改为一行 5 个“a”时,会得到下面的结果:

Regex:a?
String to search: aaaaa
Found the text "a" starting at index 0 and ending at index 1.
Found the text "a" starting at index 1 and ending at index 2.
Found the text "a" starting at index 2 and ending at index 3.
Found the text "a" starting at index 3 and ending at index 4.
Found the text "a" starting at index 4 and ending at index 5.
Found the text "" starting at index 5 and ending at index 5.

Regex:a*
String to search: aaaaa
Found the text "aaaaa" starting at index 0 and ending at index 5.
Found the text "" starting at index 5 and ending at index 5.

Regex:a+
String to search: aaaaa
Found the text "aaaaa" starting at index 0 and ending at index 5.

  在“a”出现零次或一次时,表达式a?寻找到所匹配的每一个字符。表达式a*找到了两个单独的匹配:第一次匹配到所有的字母“a”,然后是匹配到最后一个字符后面的索引5。最后,a+匹配了所有出现的字母“a”,忽略了在最后索引处“什么都没有”的存在。
  在这里,你也许会感到疑惑,开始的两个量词在遇到除了“a”的字母时会有什么结果。例如,在“”中遇到了字母“b”会发生什么呢?
  下面我们来看一下:

Regex:a?
String to search: ababaaaab
Found the text "a" starting at index 0 and ending at index 1.
Found the text "" starting at index 1 and ending at index 1.
Found the text "a" starting at index 2 and ending at index 3.
Found the text "" starting at index 3 and ending at index 3.
Found the text "a" starting at index 4 and ending at index 5.
Found the text "a" starting at index 5 and ending at index 6.
Found the text "a" starting at index 6 and ending at index 7.
Found the text "a" starting at index 7 and ending at index 8.
Found the text "" starting at index 8 and ending at index 8.
Found the text "" starting at index 9 and ending at index 9.

Regex:a*
String to search: ababaaaab
Found the text "a" starting at index 0 and ending at index 1.
Found the text "" starting at index 1 and ending at index 1.
Found the text "a" starting at index 2 and ending at index 3.
Found the text "" starting at index 3 and ending at index 3.
Found the text "aaaa" starting at index 4 and ending at index 8.
Found the text "" starting at index 8 and ending at index 8.
Found the text "" starting at index 9 and ending at index 9.

Regex:a+
String to search: ababaaaab
Found the text "a" starting at index 0 and ending at index 1.
Found the text "a" starting at index 2 and ending at index 3.
Found the text "aaaa" starting at index 4 and ending at index 8.

  即使字母“b”在单元格 1、3、8 中出现,但在这些位置上的输出报告了零长度匹配。正则表达式a?不是特意地去寻找字母“b”,它仅仅是去找字母“a”存在或者其中缺少的。如果量词允许匹配“a”零次,任何输入的字符不是“a”时将会作为零长度匹配。在前面的例子中,根据讨论的规则保证了 a 被匹配。
  对于要精确地匹配一个模式 n 次时,可以简单地在一对花括号内指定一个数值:

Regex:a{3}
String to search: aa
No match found.

Regex:a{3}
String to search: aaa
Found the text "aaa" starting at index 0 and ending at index 3.

Regex:a{3}
String to search: aaaa
Found the text "aaa" starting at index 0 and ending at index 3.

  这里,正则表确定式a{3}在一行中寻找连续出现三次的字母“a”。第一次测试失败的原由在于,输入的字符串没有足够的 a 用来匹配;第二次测试输出的字符串正好包括了三个“a”,触发了一次匹配;第三次测试也触发了一次匹配,这是由于在输出的字符串的开始部分正好有三个“a”。接下来的事情与第一次的匹配是不相关的,如果这个模式将在这一点后继续出现,那它将会触发接下来的匹配:

Regex:a{3}
String to search: aaaaaaaaa
Found the text "aaa" starting at index 0 and ending at index 3.
Found the text "aaa" starting at index 3 and ending at index 6.
Found the text "aaa" starting at index 6 and ending at index 9.

  对于需要一个模式出现至少 n 次时,可以在这个数字后面加上一个逗号(,):

Regex:a{3,}
String to search: aaaaaaaaa
Found the text "aaaaaaaaa" starting at index 0 and ending at index 9.

  输入一样的字符串,这次测试仅仅找到了一个匹配,这是由于一个中有九个“a”满足了“至少”三个“a”的要求。最后,对于指定出现次数的上限,可以在花括号添加第二个数字。

Regex:a{3,6}
String to search: aaaaaaaaa
Found the text "aaaaaa" starting at index 0 and ending at index 6.
Found the text "aaa" starting at index 6 and ending at index 9.

  这里,第一次匹配在 6 个字符的上限时被迫终止了。第二个匹配包含了剩余的三个a(这是匹配所允许最小的字符个数)。如果输入的字符串再少掉一个字母,这时将不会有第二个匹配,之后仅剩余两个 a。

  • 捕获组和字符类中的量词

  到目前为止,仅仅测试了输入的字符串包括一个字符的量词。实际上,量词仅仅可能附在一个字符后面一次,因此正则表达式abc+的意思就是“a
后面接着 b,再接着一次或者多次的c”,它的意思并不是指abc一次或者多次。然而,量词也可能附在字符类和捕获组的后面,比如,[abc]+表示一次或者多次的
a 或 b 或 c,(abc)+表示一次或者多次的“abc”组。
  我们来指定(dog)组在一行中三次进行说明。

Regex:(dog){3}
String to search: dogdogdogdogdogdog
Found the text "dogdogdog" starting at index 0 and ending at index 9.
Found the text "dogdogdog" starting at index 9 and ending at index 18.

  上面的第一个例子找到了三个匹配,这是由于量词用在了整个捕获组上。然而,把圆括号去掉,这时的量词{3}现在仅用在了字母“g”上,从而导致这个匹配失败。类似地,也能把量词应用于整个字符类:

Regex:[abc]{3}
String to search: abccabaaaccbbbc
Found the text "abc" starting at index 0 and ending at index 3.
Found the text "cab" starting at index 3 and ending at index 6.
Found the text "aaa" starting at index 6 and ending at index 9.
Found the text "ccb" starting at index 9 and ending at index 12.
Found the text "bbc" starting at index 12 and ending at index 15.
  • 贪婪、勉强和侵占量词间的不同

  在贪婪、勉强和侵占三个量词间有着细微的不同。
  贪婪量词之所以称之为“贪婪的”,这是由于它们强迫匹配器读入(或者称之为吃掉)整个输入的字符串,来优先尝试第一次匹配,如果第一次尝试匹配(对于整个输入的字符串)失败,匹配器会通过回退整个字符串的一个字符再一次进行尝试,不断地进行处理直到找到一个匹配,或者左边没有更多的字符来用于回退了。赖于在表达式中使用的量词,最终它将尝试地靠着1 或 0 个字符的匹配。
  但是,勉强量词采用相反的途径:从输入字符串的开始处开始,因此每次勉强地吞噬一个字符来寻找匹配,最终它们会尝试整个输入的字符串。
  最后,侵占量词始终是吞掉整个输入的字符串,尝试着一次(仅有一次)匹配。不像贪婪量词那样,侵占量词绝不会回退,即使这样做是允许全部的匹配成功。
  为了说明一下,看看输入的字符串是 xfooxxxxxxfoo 时。

Regex:.*foo
String to search: xfooxxxxxxfoo
Found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Regex:.*?foo
String to search: xfooxxxxxxfoo
Found the text "xfoo" starting at index 0 and ending at index 4.
Found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Regex:.*+foo
String to search: xfooxxxxxxfoo
No match found.

  第一个例子使用贪婪量词.*,寻找紧跟着字母“f”“o”“o”的“任何东西”零次或者多次。由于量词是贪婪的,表达式的.*部分第一次“吃掉”整个输入的字符串。在这一点,全部表达式不能成功地进行匹配,这是由于最后三个字母(“f”“o”“o”)已经被消耗掉了。那么匹配器会慢慢地每次回退一个字母,直到返还的“foo”在最右边出现,这时匹配成功并且搜索终止。
  然而,第二个例子采用勉强量词,因此通过首次消耗“什么也没有”作为开始。由于“foo”并没有出现在字符串的开始,它被强迫吞掉第一个字母(“x”),在0 和 4 处触发了第一个匹配。测试用具会继续处理,直到输入的字符串耗尽为止。在 4 和 13 找到了另外一个匹配。
  第三个例子的量词是侵占,所以在寻找匹配时失败了。在这种情况下,整个输入的字符串被.*+消耗了,什么都没有剩下来满足表达式末尾的“foo”。
  你可以在想抓取所有的东西,且决不回退的情况下使用侵占量词,在这种匹配不是立即被发现的情况下,它将会优于等价的贪婪量词。

时间: 2024-10-22 15:30:45

JAVA正则表达式之贪婪、勉强和侵占的相关文章

正则表达式的三种模式【贪婪、勉强、侵占】的分析

假定要分析的字符串是xfooxxxxxxfoo 模式.*foo (贪婪模式): 模式分为子模式p1(.*)和子模式p2(foo)两个部分. 其中p1中的量词匹配方式使用默认方式(贪婪型). 匹配开始时,吃入所有字符xfooxxxxxx去匹配子模式p1.匹配成功,但这样以来就没有了字符串去匹配子模式p2.本轮匹配失败:第二轮:减少p1部分的匹配量,吐出最后一个字符, 把字符串分割成xfooxxxxxxfo和o两个子字符串s1和s2. s1匹配p1, 但s2不匹配p2.本轮匹配失败:第三轮,再次减少

贪婪、勉强和侵占量词间的不同

在贪婪.勉强和侵占三个量词间有着细微的不同. 贪婪量词之所以称之为“贪婪的”,这是由于它们强迫匹配器读入(或者称之为吃掉)整个输入的字符串,来优先尝试第一次匹配,如果第一次尝试匹配(对于 整个输入的字符串)失败,匹配器会通过回退整个字符串的一个字符再一次进行尝试,不断地进行处理直到找到一个匹配,或者左边没有更多的字符来用于回退了. 赖于在表达式中使用的量词,最终它将尝试地靠着 1 或 0 个字符的匹配. 但是,勉强量词采用相反的途径:从输入字符串的开始处开始,因此每次勉强地吞噬一个字符来寻找匹配

java正则表达式语法详解及其使用代码实例

原文地址 译者序(下载代码) 正则表达式善于处理文本,对匹配.搜索和替换等操作都有意想不到的作用.正因如此,正则表达式现在是作为程序员七种基本技能之一*,因此学习和使用它在工作中都能达到很高的效率. 正则表达式应用于程序设计语言中,首次是出现在 Perl 语言,这也让 Perl 奠定了正则表达式旗手的地位.现在,它已经深入到了所有的程序设计语言中,在程序设计语言中,正则表达式可以说是标准配置了. Java 中从 JDK 1.4 开始增加了对正则表达式的支持,至此正则表达式成为了 Java 中的基

java正则表达式学习记录

正则表达式(regular expressions)是一种描述字符串集的方法,它是以字符串集中各字符串的共有特征为依据的.正则表达式可以用于搜索.编辑或者是操作文本和数据.它超出了 Java 程序设计语言的标准语法,因此有必要去学习特定的语法来构建正则表达式.正则表达式的变化是复杂的,一旦你理解了它们是如何被构造的话,你就能解析或者构建任意的正则表达式了. 字符类 字符类 [abc] a, b 或 c(简单类) [^abc] 除 a, b 或 c 之外的任意字符(取反) [a-zA-Z] a 到

Java正则表达式之语法规则

[转]Java正则表达式之语法规则 正则表达式是一种强大而灵活的文本处理工具,使用正则表达式能够以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索.一旦找到了匹配这些模式的部分,就能够随心所欲地它们进行处理.正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题:匹配.选择.编辑以及验证. 首先看一下JAVA中正则表达式的完整构造集,也可以参考java.util.regex.Pattern中的API说明. 字符 X 字符X \\ 反斜线字符 \0n 带有八进制值0的字符n(

Java正则表达式基础整理

(一)正则表达式及语法简介 String类使用正则表达式的几个方法: 正则表达式支持的合法字符: 特殊字符: 预定义字符: 方括号表达式: 圆括号表达式:用于将多个表达式组成一个子表达式,可以使用或运算符“|”,比如正则表达式:"(aa|bb|cc)"就表示匹配"aa"."bb"."cc"三个字符串中的其中一个. 边界匹配符: 贪婪.勉强.占有模式的数量标识符: (二)Java正则表达式的简单用法 两个关键类:(1)Patte

Java 正则表达式(精华)

英文:Jakob Jenkov译文:严亮链接:ifeve.com/java-regex/ Java 提供了功能强大的正则表达式API,在java.util.regex 包下.本教程介绍如何使用正则表达式API. 正则表达式 一个正则表达式是一个用于文本搜索的文本模式.换句话说,在文本中搜索出现的模式.例如,你可以用正则表达式搜索网页中的邮箱地址或超链接. 正则表达式示例 下面是一个简单的Java正则表达式的例子,用于在文本中搜索 http:// String text = "This is th

java 正则表达式语法

java 正则表达式语法 标签: 正则表达式javawindowsvbscriptscripting电话 2012-05-20 10:11 6705人阅读 评论(1) 收藏 举报  分类: javaSe(16)  目录(?)[+] 本文目标 如何使用本教程 正则表达式到底是什么? 入门 测试正则表达式 元字符 字符转义 重复 字符类 反义 替换 分组 后向引用 零宽断言 负向零宽断言 注释 贪婪与懒惰 处理选项 平衡组/递归匹配 还有些什么东西没提到 联系作者 最后,来点广告… 一些我认为你可能

聊聊 Java 正则表达式 StackOverflowError 问题及其优化

正则可以看做一门 DSL,但它却应用极其广泛,可以轻松解决很多场景下的字符串匹配.筛选问题.同时呢有句老话: " 如果你有一个问题,用正则表达式解决,那么你现在就有两个问题了." Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems. 今天我们就来聊聊 Java 正则表达式 StackO