Java正则匹配器:透明边界、锚定边界及扫描器方法详解
1. 透明边界与锚定边界
在正则表达式匹配中,透明边界和锚定边界是两个重要的概念,它们可以影响匹配的结果。
1.1 透明边界
透明边界标志(transparent-bounds flag)决定了正则引擎是否能“看到”区域边界之外的字符。默认情况下,该标志为
false
,这意味着区域边界对于前瞻、后瞻和单词边界等“查找”结构是不透明的,正则引擎无法看到区域边界之外的字符。
相关方法如下:
-
Matcher useTransparentBounds(boolean b)
:根据传入的参数将匹配器的透明边界标志设置为
true
或
false
,默认值为
false
。该方法返回匹配器对象本身,可用于方法链调用。
-
boolean hasTransparentBounds()
:如果透明边界生效,则返回
true
,否则返回
false
。
以下是一个示例,展示了默认透明边界标志为
false
时的情况:
String regex = "\\bcar\\b"; // "\b car\b"
String text = "Madagascar is best seen by car or bike.";
Matcher m = Pattern.compile(regex).matcher(text);
m.region(7, text.length());
m.find();
System.out.println("Matches starting at character " + m.start());
输出结果为:
Matches starting at character 7
这表明在区域的起始位置(“Madagascar” 的中间)匹配到了单词边界,尽管实际上这里并不是一个真正的单词边界。区域的非透明边缘“欺骗”了单词边界匹配。
如果在
find
调用之前添加
m.useTransparentBounds(true);
,则输出结果变为:
Matches starting at character 27
因为此时边界是透明的,引擎可以看到区域起始位置之前的字符
's'
是一个字母,从而禁止
\b
在该位置匹配。
1.2 锚定边界
锚定边界标志(anchoring-bounds flag)决定了行锚点(如
^
、
\A
、
$
、
\z
、
\Z
)是否在区域边界匹配。默认情况下,该标志为
true
,这意味着行锚点会在区域边界匹配,即使这些边界已经从目标字符串的起始和结束位置移动。
相关方法如下:
-
Matcher useAnchoringBounds(boolean b)
:根据传入的参数将匹配器的锚定边界标志设置为
true
或
false
,默认值为
true
。该方法返回匹配器对象本身,可用于方法链调用。
-
boolean hasAnchoringBounds()
:如果锚定边界生效,则返回
true
,否则返回
false
。
将该标志设置为
false
意味着行锚点仅在目标字符串的真正结束位置匹配(如果区域包含这些位置)。关闭锚定边界的原因与开启透明边界的原因类似,例如为了使区域的语义与用户的期望(“光标不在文本开头”)保持一致。
2. 方法链
方法链是一种将多个方法调用连接在一起的技术,它可以使代码更加简洁。例如,以下代码展示了如何使用方法链来准备匹配器并设置其选项:
Matcher m = Pattern.compile(regex).matcher(text).region(5,text.length())
.useAnchoringBounds(false).useTransparentBounds(true);
这种方式虽然不会增加额外的功能,但可以提高代码的可读性和简洁性。
3. 构建扫描器的方法
在 Java 1.5 中,新增了
hitEnd
和
requireEnd
两个匹配器方法,主要用于构建扫描器。扫描器将字符流解析为令牌流,这两个方法可以帮助扫描器决定是否应该使用刚刚完成的匹配尝试的结果来确定当前输入的正确解释。
3.1 hitEnd 方法
boolean hitEnd()
方法用于指示正则引擎在之前的匹配尝试中是否尝试检查输入的尾随结束位置之外的内容(无论该尝试最终是否成功)。如果返回
true
,则表示更多的输入可能会改变匹配结果;如果返回
false
,则表示之前的匹配结果仅基于正则引擎可用的输入,追加额外的文本不会改变结果。
该方法在 Java 1.5 中存在一个 bug,在特定情况下(当在不区分大小写模式下尝试一个可选的单字符正则组件且尝试失败时)会导致不可靠的结果。在 Java 1.6 中,该 bug 已被修复。对于 Java 1.5,可以通过以下两种方法解决:
- 关闭不区分大小写模式(至少对于有问题的子表达式)。
- 将单字符替换为其他内容,如字符类。
例如,将表达式
>=?
改为
(?-i: >=? )
,或者将
a;an;the
改为
[aA];an;the
。
3.2 requireEnd 方法
boolean requireEnd()
方法仅在成功匹配后才有意义,它用于指示正则引擎是否依赖输入的结束位置来实现该匹配。如果返回
true
,则表示额外的输入可能会导致匹配失败;如果返回
false
,则表示额外的输入可能会改变匹配的细节,但不会将成功变为失败。
3.3 示例
以下表格展示了
hitEnd
和
requireEnd
在
lookingAt
搜索后的示例:
| Regex | Text | Match | hitEnd() | requireEnd() |
| — | — | — | — | — |
|
\d+\b ; [><]=?
|
‘1234’
|
‘1234 1234’
|
true
|
true
|
|
\d+\b ; [><]=?
|
‘1234 > 567’
|
‘1234 1234 > 567’
|
false
|
false
|
|
\d+\b ; [><]=?
|
‘>’
|
‘>>’
|
true
|
false
|
|
\d+\b ; [><]=?
|
‘> 567’
|
‘>> 567’
|
false
|
false
|
|
\d+\b ; [><]=?
|
‘>=’
|
‘>= >=’
|
false
|
false
|
|
\d+\b ; [><]=?
|
‘>= 567’
|
‘>= >= 567’
|
false
|
false
|
|
\d+\b ; [><]=?
|
‘oops’
|
no match
|
false
| - |
|
( set ; setup )\b
|
‘se’
|
no match
|
true
| - |
|
( set ; setup )\b
|
‘set’
|
‘set set’
|
true
|
true
|
|
( set ; setup )\b
|
‘setu’
|
no match
|
true
| - |
|
( set ; setup )\b
|
‘setup’
|
‘setup setup’
|
true
|
true
|
|
( set ; setup )\b
|
‘set x=3’
|
‘set set x=3’
|
false
|
false
|
|
( set ; setup )\b
|
‘setup x’
|
‘setup setup x’
|
false
|
false
|
|
( set ; setup )\b
|
‘self’
|
no match
|
false
| - |
|
( set ; setup )\b
|
‘oops’
|
no match
|
false
| - |
4. 其他匹配器方法
除了上述方法外,匹配器类还提供了一些其他方法:
-
Matcher reset()
:重新初始化匹配器的大部分方面,丢弃之前成功匹配的信息,将输入位置重置为文本的起始位置,并将区域重置为“整个文本”的默认值。只有锚定边界和透明边界标志保持不变。
replaceAll
、
replaceFirst
和单参数形式的
find
方法会在内部调用
reset
,从而产生重置区域的副作用。该方法返回匹配器本身,可用于方法链调用。
-
Matcher reset(CharSequence text)
:与
reset()
方法类似,但还会将目标文本更改为新的
String
或实现
CharSequence
接口的对象。当需要将相同的正则表达式应用于多个文本块时,使用该方法重置文本比创建新的匹配器更高效。该方法返回匹配器本身,可用于方法链调用。
-
Pattern pattern()
:返回与匹配器关联的
Pattern
对象。要查看正则表达式本身,可使用
m.pattern().pattern()
。
-
Matcher usePattern(Pattern p)
:自 Java 1.5 起可用,该方法将匹配器关联的
Pattern
对象替换为提供的对象。该方法不会重置匹配器,允许你在匹配器文本的“当前位置”开始循环遍历不同的模式以查找匹配。该方法返回匹配器本身,可用于方法链调用。
-
String toString()
:自 Java 1.5 起添加,该方法返回一个包含匹配器基本信息的字符串,用于调试。字符串的内容和格式可能会发生变化。
5. 查询匹配器的目标文本
Matcher
类没有提供直接查询当前目标文本的方法,但可以通过以下方式实现:
// This pattern, used in the function below, is compiled and saved here for efficiency.
static final Pattern pNeverFail = Pattern.compile("^");
// Return the target text associated with a matcher object.
public static String text(Matcher m)
{
// Remember these items so that we can restore them later.
Integer regionStart = m.regionStart();
Integer regionEnd = m.regionEnd();
Pattern pattern = m.pattern();
// Fetch the string the only way the class allows.
String text = m.usePattern(pNeverFail).replaceFirst("");
// Put back what we changed (or might have changed).
m.usePattern(pattern).region(regionStart, regionEnd);
// Return the text
return text;
}
该方法使用一个虚拟模式和替换字符串调用
replaceFirst
来获取目标文本的未修改副本。在这个过程中,它会重置匹配器,但会恢复区域设置。虽然这不是一个特别优雅的解决方案(效率不高,并且总是返回一个
String
对象,即使匹配器的目标文本可能是不同的类),但在有更好的方法之前,它可以暂时满足需求。
6. 其他模式方法
除了主要的编译工厂方法外,
Pattern
类还包含一些辅助方法:
-
split
:该方法有两种形式,详细内容可参考相关文档。
-
String pattern()
:返回用于创建模式的正则表达式字符串参数。
-
String toString()
:自 Java 1.5 起,该方法是
pattern
方法的同义词。
-
int flags()
:返回创建模式时传递给编译工厂的标志(作为整数)。
综上所述,Java 中的正则匹配器提供了丰富的功能和方法,通过合理使用透明边界、锚定边界、方法链以及构建扫描器的方法,可以更灵活地进行正则表达式匹配。同时,对于一些特殊情况(如
hitEnd
方法的 bug),也有相应的解决办法。在实际应用中,根据具体需求选择合适的方法和标志,能够提高匹配的准确性和效率。
Java正则匹配器:透明边界、锚定边界及扫描器方法详解
7. 方法使用流程总结
为了更清晰地展示各个方法的使用流程,下面通过 mermaid 格式的流程图来说明。
graph LR
A[开始] --> B[编译正则表达式]
B --> C[创建匹配器]
C --> D{是否需要设置区域}
D -- 是 --> E[设置区域]
D -- 否 --> F{是否需要设置锚定边界}
E --> F
F -- 是 --> G[设置锚定边界]
F -- 否 --> H{是否需要设置透明边界}
G --> H
H -- 是 --> I[设置透明边界]
H -- 否 --> J{是否需要进行匹配操作}
I --> J
J -- 是 --> K[进行匹配操作]
J -- 否 --> L[结束]
K --> M{是否需要使用 hitEnd 或 requireEnd}
M -- 是 --> N[使用 hitEnd 或 requireEnd 判断]
M -- 否 --> O{是否需要替换操作}
N --> O
O -- 是 --> P[进行替换操作]
O -- 否 --> Q{是否需要查询目标文本}
P --> Q
Q -- 是 --> R[查询目标文本]
Q -- 否 --> S[结束]
R --> S
这个流程图展示了从编译正则表达式开始,到最终结束的整个过程。在这个过程中,可以根据具体需求选择是否设置区域、锚定边界、透明边界,进行匹配操作、替换操作以及查询目标文本等。
8. 实际应用场景分析
下面通过几个实际应用场景来进一步说明这些方法的使用。
8.1 文本编辑中的搜索替换
在文本编辑应用中,用户可能会在光标位置之后进行搜索和替换操作。例如,用户在编辑一段文本时,光标位于 “Madagascar is much too large to see on foot, so you’ll need a car.” 中的某个位置,想要将 “\bcar\b” 替换为 “automobile”。可以使用以下代码实现:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextEditSearchReplace {
public static void main(String[] args) {
String regex = "\\bcar\\b";
String text = "Madagascar is much too large to see on foot, so you’ll need a car.";
Matcher m = Pattern.compile(regex).matcher(text);
// 假设光标位置为 7
m.region(7, text.length());
m.useTransparentBounds(true);
String newText = m.replaceAll("automobile");
System.out.println(newText);
}
}
在这个例子中,通过设置区域和透明边界,确保只在光标位置之后进行搜索和替换,并且避免了在区域起始位置错误匹配单词边界的问题。
8.2 扫描器的实现
在编译器等应用中,需要将字符流解析为令牌流。可以使用
hitEnd
和
requireEnd
方法来实现一个简单的扫描器。以下是一个示例代码:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SimpleScanner {
public static void main(String[] args) {
String regex = "\\d+\\b ; [><]=?";
String text = "1234 > 567";
Matcher m = Pattern.compile(regex).matcher(text);
if (m.lookingAt()) {
if (m.hitEnd()) {
System.out.println("需要更多输入才能确定结果");
} else if (m.requireEnd()) {
System.out.println("额外输入可能导致匹配失败");
} else {
System.out.println("匹配成功,结果确定");
}
} else {
System.out.println("匹配失败");
}
}
}
在这个例子中,通过
lookingAt
方法进行匹配,然后使用
hitEnd
和
requireEnd
方法来判断是否需要更多输入或额外输入是否会影响匹配结果。
9. 注意事项和技巧
在使用这些方法时,还需要注意以下几点:
-
标志的默认值
:锚定边界标志默认值为
true
,透明边界标志默认值为
false
。在使用时,要根据具体需求进行设置。
-
hitEnd 方法的 bug
:在 Java 1.5 中,
hitEnd
方法存在 bug,需要使用前面提到的解决方法。
-
方法链的使用
:方法链可以使代码更简洁,但在调试和阅读时可能会增加难度。可以根据实际情况选择是否使用方法链。
-
重置方法的影响
:
reset
方法会重置大部分匹配器的信息,但不会重置锚定边界和透明边界标志。在使用时要注意这一点。
10. 总结
通过以上内容的介绍,我们对 Java 中的正则匹配器有了更深入的了解。以下是对各个关键知识点的总结表格:
| 知识点 | 描述 |
| — | — |
| 透明边界 | 决定正则引擎是否能看到区域边界之外的字符,默认值为
false
,可通过
useTransparentBounds
方法设置 |
| 锚定边界 | 决定行锚点是否在区域边界匹配,默认值为
true
,可通过
useAnchoringBounds
方法设置 |
| 方法链 | 将多个方法调用连接在一起,使代码更简洁 |
| hitEnd 方法 | 指示正则引擎在之前的匹配尝试中是否尝试检查输入的尾随结束位置之外的内容,Java 1.5 存在 bug |
| requireEnd 方法 | 仅在成功匹配后有意义,指示正则引擎是否依赖输入的结束位置来实现匹配 |
| 其他匹配器方法 | 如
reset
、
reset(CharSequence text)
、
pattern()
、
usePattern(Pattern p)
、
toString()
等 |
| 查询目标文本 | 可通过特定方法实现,虽然不是特别优雅,但能暂时满足需求 |
| 其他模式方法 | 如
split
、
pattern()
、
toString()
、
flags()
等 |
在实际应用中,我们可以根据具体需求选择合适的方法和标志,灵活运用这些功能来实现高效、准确的正则表达式匹配。同时,要注意一些特殊情况和注意事项,以避免出现问题。通过合理使用这些方法,能够提高代码的可读性和可维护性,提升开发效率。
超级会员免费看
5037

被折叠的 条评论
为什么被折叠?



