# Regex 正则表达式
# 概述
MT 正则表达式库提供了一套与 Java 标准正则表达式兼容的 API,同时针对文本编辑器场景进行了优化。与系统的 java.util.regex 相比,MT 正则库可以直接在 BufferedText 等文本缓冲区上进行匹配,避免了 toString() 转换带来的性能开销。
主要特性:
- 性能优化:直接在文本缓冲区上匹配,避免字符串转换
- 超时控制:支持设置匹配超时时间,防止复杂正则导致卡顿
- 快照功能:可以保存匹配状态,避免重复计算
- 扩展功能:提供
lookingAt(int)等扩展方法,支持完整单词匹配标志 - 替换增强:支持捕获组引用和大小写转换
# 核心类
# Regex
正则表达式工具类,提供静态方法用于编译正则表达式和验证替换模板。
# 编译正则表达式
// 编译正则表达式
Pattern pattern = Regex.compile("\\d+");
// 使用指定标志编译正则表达式
Pattern pattern = Regex.compile(
"hello",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
);
参数说明:
regex:正则表达式字符串flags:匹配标志位,可使用|组合多个标志
# 验证替换模板
// 验证替换模板语法是否正确
try {
Regex.checkReplacementTemplate(pattern, "替换为:$1");
} catch (PatternSyntaxException e) {
// 替换模板语法错误
Log.e("替换模板验证失败", e);
}
功能说明:
checkReplacementTemplate() 方法用于验证替换模板的语法是否正确,包括:
- 转义序列是否完整(如
\n、\t、\\等) $符号后是否跟随了数字${}语法格式是否正确- 命名捕获组是否存在(如果提供了 pattern 参数)
注意:该方法不会检查数字序号的捕获组是否真实存在。例如,即使正则表达式只有 2 个捕获组,$3 也能通过语法验证,但在实际替换时不会产生任何输出。
# Pattern
正则表达式模式,表示已编译的正则表达式。
# 匹配标志
Pattern 提供了以下匹配标志,可以通过 | 运算符组合使用:
| 标志 | 说明 | 嵌入标志 |
|---|---|---|
UNIX_LINES | Unix 行模式,只识别 \n 作为行终止符 | (?d) |
CASE_INSENSITIVE | 忽略大小写匹配 | (?i) |
COMMENTS | 注释模式,忽略空白字符和 # 开头的注释 | (?x) |
MULTILINE | 多行模式,^ 和 $ 匹配行的开始和结束 | (?m) |
LITERAL | 字面量模式,将模式字符串视为普通字符 | - |
DOTALL | 点号通配模式,使 . 匹配包括换行符在内的任意字符 | (?s) |
UNICODE_CASE | Unicode 大小写折叠,与 CASE_INSENSITIVE 配合使用 | (?u) |
CANON_EQ | 规范等价,按 Unicode 规范分解进行字符匹配 | - |
UNICODE_CHARACTER_CLASS | Unicode 字符类,启用 Unicode 版本的预定义字符类 | (?U) |
MATCH_WHOLE_WORD | 全词匹配(MT 扩展) | - |
标志说明:
- CASE_INSENSITIVE:默认仅对 US-ASCII 字符生效,配合
UNICODE_CASE可支持 Unicode 字符,可能影响性能 - DOTALL:启用后
.可以匹配换行符,适用于跨行匹配场景 - UNICODE_CASE:配合
CASE_INSENSITIVE使用时按 Unicode 标准进行大小写匹配,可能影响性能 - UNICODE_CHARACTER_CLASS:启用 Unicode 版本的
\d、\w等字符类,隐含启用UNICODE_CASE,可能影响性能 - MATCH_WHOLE_WORD:MT 正则自定义扩展标志,用于精确的完整单词匹配
# 创建匹配器
Pattern pattern = Regex.compile("\\w+");
Matcher matcher = pattern.matcher("Hello World");
# 获取模式信息
String regex = pattern.pattern(); // 获取正则表达式字符串
int flags = pattern.flags(); // 获取标志位
# Matcher
正则表达式匹配器,用于对文本序列执行匹配操作。
# 基本匹配方法
# matches() - 完全匹配
判断整个输入序列是否与正则表达式完全匹配。
Pattern pattern = Regex.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher matcher = pattern.matcher("2024-01-15");
if (matcher.matches()) {
Log.i("完全匹配成功");
}
# find() - 查找匹配
从当前位置开始查找下一个匹配的子序列。
Pattern pattern = Regex.compile("\\d+");
Matcher matcher = pattern.matcher("价格:100元,折扣:20元");
while (matcher.find()) {
Log.i("找到数字:" + matcher.group());
}
// 输出:100, 20
可以从指定位置开始查找:
if (matcher.find(10)) {
Log.i("从位置10开始找到:" + matcher.group());
}
# lookingAt() - 前缀匹配
判断输入序列的开头是否与正则表达式匹配,不要求匹配整个序列。
Pattern pattern = Regex.compile("https?://");
Matcher matcher = pattern.matcher("http://example.com/path");
if (matcher.lookingAt()) {
Log.i("以 http:// 或 https:// 开头");
}
MT 正则扩展了 lookingAt(int start) 方法,可以从指定位置开始匹配:
// 从位置 7 开始匹配
if (matcher.lookingAt(7)) {
Log.i("从位置7开始匹配成功");
}
# 获取匹配结果
在调用 matches()、find() 或 lookingAt() 成功后,可以获取匹配的详细信息。
# 获取匹配内容
String matched = matcher.group(); // 获取整个匹配内容
String group1 = matcher.group(1); // 获取第1个捕获组
# 获取匹配位置
int start = matcher.start(); // 获取匹配起始位置
int end = matcher.end(); // 获取匹配结束位置
int group1Start = matcher.start(1); // 获取第1个捕获组起始位置
int group1End = matcher.end(1); // 获取第1个捕获组结束位置
# 获取捕获组数量
int count = matcher.groupCount(); // 获取捕获组数量(不包括组0)
# 匹配状态快照(MT 扩展)
toSnapshot() 方法可以创建当前匹配状态的快照,用于保存匹配结果以便后续访问。
Pattern pattern = Regex.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher matcher = pattern.matcher("user@example.com");
if (matcher.find()) {
// 保存匹配快照
MatcherSnapshot snapshot = matcher.toSnapshot();
// 继续使用 matcher 进行其他操作...
// 稍后可以从快照获取之前的匹配结果
String email = snapshot.group(0);
String username = snapshot.group(1);
String domain = snapshot.group(2);
}
快照的优势:
- 避免重复调用
find()或matches() - 可以保存多个匹配结果
- 在异步场景中缓存匹配信息
# 替换操作
# replaceAll() - 替换所有匹配
Pattern pattern = Regex.compile("\\d+");
Matcher matcher = pattern.matcher("价格100元,优惠20元");
String result = matcher.replaceAll("**");
// 结果:价格**元,优惠**元
# replaceFirst() - 替换首个匹配
String result = matcher.replaceFirst("**");
// 结果:价格**元,优惠20元
# 使用捕获组引用
替换字符串支持引用捕获组:
Pattern pattern = Regex.compile("(\\w+)@(\\w+)");
Matcher matcher = pattern.matcher("user@example");
String result = matcher.replaceAll("$1 at $2");
// 结果:user at example
支持的捕获组引用格式:
$n:引用第 n 个捕获组${n}:引用第 n 个捕获组(更清晰)${name}:引用命名捕获组
# 转义和特殊字符
替换字符串支持以下转义序列:
| 转义序列 | 说明 |
|---|---|
\n | 换行符 |
\r | 回车符 |
\t | 制表符 |
\$ | 字面量 $ 符号 |
\\ | 字面量 \ 符号 |
# 大小写转换
替换字符串支持对捕获组内容进行大小写转换:
| 转换符 | 说明 |
|---|---|
\l | 将后续一个字符转为小写 |
\u | 将后续一个字符转为大写 |
\L | 将后续所有字符转为小写 |
\U | 将后续所有字符转为大写 |
示例:
Pattern pattern = Regex.compile("(\\w+)");
Matcher matcher = pattern.matcher("hello world");
// 将每个单词首字母大写
matcher.replaceAll("\\u\\L$1"); // 结果:Hello World
// 全部转为大写
matcher.replaceAll("\\U$1"); // 结果:HELLO WORLD
// 全部转为小写
matcher.replaceAll("\\L$1"); // 结果:hello world
# 高级替换
使用 appendReplacement() 和 appendTail() 进行逐步替换:
Pattern pattern = Regex.compile("\\d+");
Matcher matcher = pattern.matcher("价格100元,优惠20元");
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
int num = Integer.parseInt(matcher.group());
// 将数字翻倍
matcher.appendReplacement(sb, String.valueOf(num * 2));
}
matcher.appendTail(sb);
Log.i(sb.toString()); // 价格200元,优惠40元
使用 expandReplacement() 展开替换模板:
if (matcher.find()) {
String replacement = matcher.expandReplacement("匹配到:$0");
Log.i(replacement);
}
# 超时控制
为了防止复杂正则表达式导致性能问题,MT 正则支持设置匹配超时时间。
Matcher matcher = pattern.matcher(text);
matcher.setTimeoutMillis(3000); // 设置超时时间为3秒
if (matcher.find()) {
// 匹配成功
} else {
// 匹配失败或超时
}
默认超时时间:2000 毫秒(2 秒)
超时行为:超时后匹配方法会直接返回 false,不会抛出异常。
# 区域控制
可以限制匹配只在文本的指定范围内进行:
Matcher matcher = pattern.matcher("Hello World");
matcher.region(0, 5); // 只匹配 "Hello" 部分
if (matcher.find()) {
Log.i("在区域内找到:" + matcher.group());
}
int start = matcher.regionStart(); // 获取区域起始位置
int end = matcher.regionEnd(); // 获取区域结束位置
# 边界控制
# 透明边界模式
// 默认使用不透明边界
matcher.useTransparentBounds(false);
// 启用透明边界,前瞻、后顾等可以看到区域外的内容
matcher.useTransparentBounds(true);
boolean isTransparent = matcher.hasTransparentBounds();
# 锚定边界模式
// 默认启用锚定边界,^ 和 $ 匹配区域边界
matcher.useAnchoringBounds(true);
// 禁用锚定边界,^ 和 $ 只匹配整个输入序列的边界
matcher.useAnchoringBounds(false);
boolean isAnchoring = matcher.hasAnchoringBounds();
# 匹配状态检查
# hitEnd() - 检查是否到达输入末尾
判断匹配器在搜索过程中是否读取到了输入序列的最后一个字符。
matcher.find();
if (matcher.hitEnd()) {
Log.i("匹配过程中到达了输入末尾");
}
# requireEnd() - 检查匹配的稳定性
判断更多输入是否可能影响当前的匹配结果。
matcher.find();
if (matcher.requireEnd()) {
Log.i("更多输入可能导致当前匹配失效");
} else {
Log.i("更多输入不会导致当前匹配失效");
}
说明:
- 如果此方法返回
true且找到了匹配,则更多输入可能导致匹配丢失 - 如果此方法返回
false且找到了匹配,则更多输入可能改变匹配但不会丢失匹配 - 如果没有找到匹配,则此方法的返回值无意义
典型应用场景:在增量输入场景(如实时搜索)中,通过 requireEnd() 判断是否需要等待更多输入后再确定匹配结果。
# 重置匹配器
// 重置匹配器状态
matcher.reset();
// 重置并设置新的输入文本
matcher.reset("新的文本内容");
# 获取关联对象
Pattern pattern = matcher.pattern(); // 获取关联的 Pattern
CharSequence text = matcher.getText(); // 获取关联的文本序列
# 使用示例
# 基本匹配和查找
import bin.mt.plugin.api.regex.Matcher;
import bin.mt.plugin.api.regex.Pattern;
import bin.mt.plugin.api.regex.Regex;
// 编译正则表达式
Pattern pattern = Regex.compile("\\b\\w+@\\w+\\.\\w+\\b");
Matcher matcher = pattern.matcher("联系方式:admin@example.com 或 user@test.org");
// 查找所有邮箱地址
while (matcher.find()) {
String email = matcher.group();
int start = matcher.start();
int end = matcher.end();
Log.i("找到邮箱:" + email + " 位置:[" + start + ", " + end + ")");
}
# 捕获组提取
// 提取日期的年月日
Pattern pattern = Regex.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher matcher = pattern.matcher("今天是2024-01-15");
if (matcher.find()) {
String year = matcher.group(1);
String month = matcher.group(2);
String day = matcher.group(3);
Log.i("年:" + year + ", 月:" + month + ", 日:" + day);
}
# 内容替换和转换
// 将 Markdown 链接转换为 HTML
Pattern pattern = Regex.compile("\\[([^\\]]+)\\]\\(([^)]+)\\)");
Matcher matcher = pattern.matcher("查看[文档](http://example.com)");
String html = matcher.replaceAll("<a href=\"$2\">$1</a>");
// 结果:查看<a href="http://example.com">文档</a>
// 驼峰命名转下划线
Pattern camelPattern = Regex.compile("([a-z])([A-Z])");
Matcher camelMatcher = camelPattern.matcher("userName");
String snakeCase = camelMatcher.replaceAll("$1_\\L$2");
// 结果:user_name
# 文本编辑器中的应用
在文本编辑器中使用 MT 正则库处理 BufferedText:
import bin.mt.plugin.api.text.BufferedText;
void searchInEditor(TextEditor editor) {
BufferedText text = editor.getText();
// 方式1:使用 BufferedText.matcher() (推荐)
Pattern pattern = Regex.compile("TODO:.*");
Matcher matcher = text.matcher(pattern);
// 方式2:使用 Pattern.matcher()
// Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String todo = matcher.group();
Log.i("找到 TODO:" + todo);
}
}
性能提示:
对于 BufferedText 文本,推荐使用 MT 正则库的 BufferedText.matcher() 方法创建匹配器。MT 正则库直接在文本缓冲区上进行匹配,避免了 toString() 转换。
错误示范:
// ❌ 不要使用系统正则库处理 BufferedText
java.util.regex.Pattern sysPattern = java.util.regex.Pattern.compile("pattern");
sysPattern.matcher(bufferedText); // 这会调用 toString(),导致性能问题
正确做法:
// ✅ 使用 MT 正则库
Pattern mtPattern = Regex.compile("pattern");
bufferedText.matcher(mtPattern); // 或 mtPattern.matcher(bufferedText)
# 高级匹配控制
Pattern pattern = Regex.compile("test");
Matcher matcher = pattern.matcher("This is a test in a test file");
// 设置超时时间
matcher.setTimeoutMillis(5000);
// 设置匹配区域
matcher.region(10, 20);
// 启用透明边界
matcher.useTransparentBounds(true);
// 查找匹配
if (matcher.find()) {
MatcherSnapshot snapshot = matcher.toSnapshot();
// 保存匹配快照供后续使用
}
# 注意事项
性能考虑:
- 复杂的正则表达式可能导致性能问题,建议设置合理的超时时间
- 对于大文本,考虑使用区域控制限制匹配范围
- 在文本编辑器中处理
BufferedText时,务必使用 MT 正则库而非系统库
标志组合:
UNICODE_CASE需要与CASE_INSENSITIVE配合使用UNICODE_CHARACTER_CLASS隐含启用UNICODE_CASE- Unicode 相关标志可能影响性能,仅在必要时使用
捕获组引用:
- 捕获组编号从 1 开始,0 表示整个匹配
- 如果引用的捕获组不存在或未参与匹配,
group()返回null - 替换模板语法验证不检查数字捕获组是否存在
边界控制:
- 默认使用不透明边界和锚定边界
- 修改边界设置会影响
^、$、\b等边界匹配符的行为 - 在使用
region()时需要特别注意边界设置
线程安全:
Pattern对象是线程安全的,可以在多个线程间共享Matcher对象不是线程安全的,每个线程应使用独立的匹配器实例
# 相关接口
- Text 文本处理 - BufferedText 和文本操作
- TextEditor 文本编辑器 - 文本编辑器核心功能
- TextEditorFloatingMenu 浮动菜单 - 文本选择浮动菜单
- TextEditorToolMenu 工具菜单 - 文本编辑器工具菜单
- TextEditorQuickAction 快捷功能 - 文本编辑器快捷功能