Kotlin Regex的find方法起始索引异常行为及高效解决方案
问题解析与解决方案
为什么find(1)返回的还是"This"?
问题出在正则的正向回顾断言(lookbehind)上。你的正则(?<=[ \t\n$PUNC])(\w+)里的(?<=[ \t\n$PUNC])是零宽度断言,它只会检查当前位置左侧是否存在空白或标点,但不会受find方法指定的起始索引限制——哪怕你从索引1开始扫描,它仍然会回头检查索引0的字符。
当调用find(1)时,匹配器从索引1(字符'T')开始:
- 检查索引1左侧的字符是'#',属于
$PUNC,满足回顾条件; - 紧接着的
\w+匹配从索引1开始的连续字母"This",所以直接返回该结果,不会继续向后查找"is"。
高效解决办法(无需生成子串)
方案1:跟踪上一次匹配的结束位置(推荐)
这是遍历正则匹配的标准做法,既高效又能精准控制查找范围:
val possibleString = "#This is a comment" val regex = "(?<=[ \t\n$PUNC])(\\w+)".toRegex() // 第一次查找 var currentMatch = regex.find(possibleString) println(currentMatch?.value) // 输出: This // 从上次匹配的结束位置开始下一次查找 currentMatch?.let { currentMatch = regex.find(possibleString, it.range.last + 1) } println(currentMatch?.value) // 输出: is
这种方式直接跳过已匹配的区域,完全避免了重复匹配,也不会占用额外内存。
方案2:调整正则表达式
如果需要更灵活的起始位置控制,可以修改正则,同时兼容字符串开头的单词(原正则无法匹配开头无分隔符的单词):
// 匹配规则:要么是字符串开头的单词,要么是前面有分隔符的单词 val regex = "(?:^|[ \t\n$PUNC])(\\w+)".toRegex()
使用这个正则时,需要通过groupValues[1]获取实际的单词内容(因为整个匹配包含了前面的分隔符或空字符串)。如果要从指定索引start开始查找有效单词,可以循环调用find,直到匹配的起始位置符合要求:
val start = 1 var targetMatch: MatchResult? = null var currentMatch = regex.find(possibleString) while (currentMatch != null) { // 计算单词的实际起始位置(跳过前面的分隔符) val wordStart = currentMatch.range.start + if (currentMatch.value[0].isLetterOrDigit()) 0 else 1 if (wordStart >= start) { targetMatch = currentMatch break } currentMatch = regex.find(possibleString, currentMatch.range.last + 1) } println(targetMatch?.groupValues[1]) // 输出: is
内容的提问来源于stack exchange,提问作者qwerty
相关产品推荐
相关产品推荐

