1. 概述

正则表达式(Regular Expressions,简称 RegEx)是一种强大的文本处理工具,广泛应用于字符串匹配、替换、提取等场景。本文将从基础语法讲起,逐步深入到实际应用,帮助你掌握 Kotlin 中正则表达式的使用。

我们将学习如何构建各种复杂度的正则表达式,并通过实际案例练习来理解如何将问题转化为正则表达式解决方案。

2. 正则表达式简介

正则表达式是用于匹配字符串中特定字符序列的公式。它的核心思想是:在一段未知或部分未知的文本中,提取满足某些条件的子串。

正则表达式在自然语言处理(NLP)、文本解析、词法分析、字符串替换、信息检索等领域中非常常见。它几乎被所有主流编程语言支持,包括 Kotlin、Java、Scala、Groovy 等。

3. 正则表达式语法

3.1 单个字符匹配

最简单的正则表达式就是直接匹配一个字符。例如:

val r = "A".toRegex()
val s = "aaAbbBccC"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["A"]

如果我们要匹配多个相同的字符,比如两个连续的 a

val r = "aa".toRegex()
val s = "aabaA"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["aa"]

3.2 字符组与方括号

我们可以使用方括号 [] 来表示多个可选字符。例如,匹配 aA

val r = "[aA]".toRegex()
val s = "aaAbbBccC"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["a", "a", "A"]

如果我们想匹配 a 后面跟一个 aA

val r = "a[aA]".toRegex()
val s = "aabaA"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["aa", "aA"]

3.3 字符范围

使用 - 可以表示字符范围。例如,匹配所有大写字母:

val r = "[A-Z]".toRegex()
val s = "aaAbbBccC"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["A", "b", "B", "C"]

匹配所有字母(大小写)和数字:

val r = "[a-zA-Z0-9]".toRegex()
val s = "abcABC123"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["a", "b", "c", "A", "B", "C", "1", "2", "3"]

3.4 特殊字符:单词与数字

  • .:匹配任意单个字符(除换行符)
  • \w:匹配字母、数字、下划线
  • \W:匹配非字母、数字、下划线字符
  • \d:匹配数字
  • \D:匹配非数字字符

示例:

val r = "\\w".toRegex()
val s = "a_B1?"
println(r.findAll(s).map { it.value }.toList()) // 输出 ["a", "_", "B", "1"]

3.5 空字符串与空白字符

  • \b:匹配单词边界
  • \s:匹配空白字符(空格、制表符、换行符等)
  • \S:匹配非空白字符

示例:

val r = "\\bship\\b".toRegex()
val s1 = "flagship"
val s2 = "ship"
println(r.find(s1)) // 输出 null
println(r.find(s2)?.value) // 输出 "ship"

3.6 量词

量词用于指定字符或分组的重复次数:

量词 含义
? 0 次或 1 次
* 0 次或多次
+ 1 次或多次
{n} 恰好 n 次
{n,} 至少 n 次
{n,m} 至少 n 次,最多 m 次

示例:

val r = "ni*ce".toRegex()
val s1 = "nice"
val s2 = "niiice"
val s3 = "nce"
println(r.findAll(s1).map { it.value }.toList()) // ["nice"]
println(r.findAll(s2).map { it.value }.toList()) // ["niiice"]
println(r.findAll(s3).map { it.value }.toList()) // ["nce"]

3.7 分组

使用 () 可以将多个字符或表达式组合成一个整体,并对整个组应用量词。

示例:

val r = "(abc){2}".toRegex()
val s = "abcabc"
println(r.find(s)?.value) // 输出 "abcabc"

3.8 逻辑 OR

使用 | 可以实现逻辑“或”操作。

示例:

val r = "gr(a|e)y".toRegex()
val s1 = "gray"
val s2 = "grey"
println(r.find(s1)?.value) // 输出 "gray"
println(r.find(s2)?.value) // 输出 "grey"

3.9 行首与行尾

  • ^:匹配行首
  • $:匹配行尾

示例:

val r = "^0,0,0,$".toRegex()
val s = "0,0,0,"
println(r.find(s)?.value) // 输出 "0,0,0,"

3.10 贪婪与非贪婪

默认情况下,正则表达式是贪婪的(尽可能匹配更多字符),可以通过添加 ? 变为非贪婪模式。

示例:

val r = "\\d+?".toRegex()
val s = "555"
println(r.findAll(s).map { it.value }.toList()) // ["5", "5", "5"]

4. 实战示例

4.1 匹配 Mr.、Mrs.、Ms.

我们希望构建两个正则表达式:

  • 匹配男性称谓(Mr.)
  • 匹配女性称谓(Mrs. 或 Ms.)

示例:

val r1 = "Mr\\.".toRegex()
val r2 = "Mr?s\\.".toRegex()

val s1 = "Mr."
val s2 = "Mrs."
val s3 = "Ms."

println(r1.find(s1)?.value) // 输出 "Mr."
println(r2.find(s2)?.value) // 输出 "Mrs."
println(r2.find(s3)?.value) // 输出 "Ms."

4.2 加入 Miss

如果还要匹配 Miss(无点号):

val r = "M(r?s\\.|iss)".toRegex()

val s1 = "Mr."
val s2 = "Mrs."
val s3 = "Ms."
val s4 = "Miss"

println(r.find(s1)?.value) // 输出 "Mr."
println(r.find(s2)?.value) // 输出 "Mrs."
println(r.find(s3)?.value) // 输出 "Ms."
println(r.find(s4)?.value) // 输出 "Miss"

4.3 验证电子邮件地址

目标:提取所有合法的电子邮件地址。

规则:

  • 包含 @
  • 域名部分包含 .,如 example.com
  • 顶级域名至少两个字符
  • 可包含字母、数字、下划线、连字符、加号、百分号等

完整正则表达式:

val emailRegex = "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b".toRegex()
val text = "请发送邮件至 john.doe@example.com 或 jane_doe+work@sub.domain.co.uk"
val emails = emailRegex.findAll(text).map { it.value }.toList()
println(emails) // 输出 ["john.doe@example.com", "jane_doe+work@sub.domain.co.uk"]

5. 小结

本文系统介绍了 Kotlin 中正则表达式的基础语法和实际应用技巧。通过掌握这些内容,你可以更高效地进行文本处理、数据清洗、格式验证等任务。

✅ 正则表达式虽然强大,但也要注意其可读性和性能问题。
❌ 避免过度使用正则表达式处理复杂结构(如 HTML、JSON)。
⚠️ 使用时注意转义字符,避免语法错误。

如果你经常处理字符串,正则表达式是不可或缺的工具之一。熟练掌握它,能让你在日常开发中事半功倍。


原始标题:Regular Expressions in Kotlin

» 下一篇: Levenshtein 距离计算