Kotlin正则表达式高效使用技巧
2021-02-196.7k 阅读
Kotlin 正则表达式基础
在 Kotlin 中,正则表达式是用于匹配和操作字符串的强大工具。正则表达式由字符和操作符组成的模式,用于定义搜索模式。Kotlin 对正则表达式的支持主要通过 Regex
类实现。
创建 Regex 对象
要在 Kotlin 中使用正则表达式,首先需要创建一个 Regex
对象。可以通过以下几种方式创建:
- 直接字符串创建:
val regex = Regex("pattern")
这里的 "pattern"
是正则表达式的字符串表示。例如,要匹配数字,可以使用 Regex("\\d")
。注意,在 Kotlin 字符串中,反斜杠 \
需要转义,所以是 \\d
。
2. 使用 toRegex()
扩展函数:
val pattern = "pattern".toRegex()
这是一种更简洁的创建 Regex
对象的方式,例如 "\d+".toRegex()
可以创建匹配一个或多个数字的正则表达式。
简单匹配
一旦有了 Regex
对象,就可以使用它进行匹配操作。Regex
类提供了多个方法用于匹配字符串。
matches
方法: 这个方法用于判断整个字符串是否与正则表达式匹配。
val regex = Regex("abc")
val str1 = "abc"
val str2 = "abcd"
println(regex.matches(str1)) // true
println(regex.matches(str2)) // false
containsMatchIn
方法: 判断字符串中是否包含与正则表达式匹配的子串。
val regex = Regex("abc")
val str1 = "123abc456"
val str2 = "123def456"
println(regex.containsMatchIn(str1)) // true
println(regex.containsMatchIn(str2)) // false
正则表达式语法
字符类
- 预定义字符类:
\d
:匹配任意一个数字字符,等价于[0-9]
。
val regex = Regex("\\d") val str = "a1b" println(regex.containsMatchIn(str)) // true
\D
:匹配任意一个非数字字符,等价于[^0-9]
。
val regex = Regex("\\D") val str = "a1b" println(regex.containsMatchIn(str)) // true
\w
:匹配任意一个单词字符,包括字母、数字和下划线,等价于[a-zA-Z0-9_]
。
val regex = Regex("\\w") val str = "a_1" println(regex.containsMatchIn(str)) // true
\W
:匹配任意一个非单词字符,等价于[^a-zA-Z0-9_]
。
val regex = Regex("\\W") val str = "a_1!" println(regex.containsMatchIn(str)) // true
\s
:匹配任意一个空白字符,包括空格、制表符、换行符等,等价于[ \t\n\r\f]
。
val regex = Regex("\\s") val str = "a b" println(regex.containsMatchIn(str)) // true
\S
:匹配任意一个非空白字符,等价于[^ \t\n\r\f]
。
val regex = Regex("\\S") val str = "a b" println(regex.containsMatchIn(str)) // true
- 自定义字符类:
可以使用方括号
[]
定义自己的字符类。例如,[aeiou]
匹配任意一个元音字母。
val regex = Regex("[aeiou]")
val str = "apple"
println(regex.containsMatchIn(str)) // true
也可以使用连字符 -
表示范围,如 [a - z]
匹配任意小写字母,[0 - 9]
匹配任意数字。
val regex1 = Regex("[a - z]")
val regex2 = Regex("[0 - 9]")
val str1 = "a"
val str2 = "1"
println(regex1.containsMatchIn(str1)) // true
println(regex2.containsMatchIn(str2)) // true
使用 ^
作为字符类的第一个字符表示取反,如 [^0 - 9]
匹配任意非数字字符。
val regex = Regex("[^0 - 9]")
val str = "a"
println(regex.containsMatchIn(str)) // true
量词
- 贪婪量词:
*
:匹配前面的字符零次或多次。例如,a*
可以匹配空字符串、a
、aa
、aaa
等。
val regex = Regex("a*") val str1 = "" val str2 = "a" val str3 = "aaa" println(regex.matches(str1)) // true println(regex.matches(str2)) // true println(regex.matches(str3)) // true
+
:匹配前面的字符一次或多次。例如,a+
可以匹配a
、aa
、aaa
等,但不匹配空字符串。
val regex = Regex("a+") val str1 = "" val str2 = "a" val str3 = "aaa" println(regex.matches(str1)) // false println(regex.matches(str2)) // true println(regex.matches(str3)) // true
?
:匹配前面的字符零次或一次。例如,a?
可以匹配空字符串或a
。
val regex = Regex("a?") val str1 = "" val str2 = "a" println(regex.matches(str1)) // true println(regex.matches(str2)) // true
{n}
:匹配前面的字符恰好n
次。例如,a{3}
只匹配aaa
。
val regex = Regex("a{3}") val str1 = "aa" val str2 = "aaa" println(regex.matches(str1)) // false println(regex.matches(str2)) // true
{n,}
:匹配前面的字符至少n
次。例如,a{3,}
匹配aaa
、aaaa
、aaaaa
等。
val regex = Regex("a{3,}") val str1 = "aa" val str2 = "aaa" println(regex.matches(str1)) // false println(regex.matches(str2)) // true
{n,m}
:匹配前面的字符至少n
次,最多m
次。例如,a{2,4}
匹配aa
、aaa
、aaaa
。
val regex = Regex("a{2,4}") val str1 = "a" val str2 = "aa" val str3 = "aaa" val str4 = "aaaa" val str5 = "aaaaa" println(regex.matches(str1)) // false println(regex.matches(str2)) // true println(regex.matches(str3)) // true println(regex.matches(str4)) // true println(regex.matches(str5)) // false
- 懒惰量词:
在贪婪量词后面加上
?
就变成了懒惰量词。懒惰量词会尽可能少地匹配字符。*?
:匹配前面的字符零次或多次,但尽可能少匹配。例如,对于字符串aaa
,a*?
只会匹配空字符串,因为它尽可能少地匹配。
val regex = Regex("a*?") val str = "aaa" val matchResult = regex.find(str) matchResult?.let { println(it.value) } // 输出空字符串
+?
:匹配前面的字符一次或多次,但尽可能少匹配。例如,对于字符串aaa
,a+?
只会匹配一个a
。
val regex = Regex("a+?") val str = "aaa" val matchResult = regex.find(str) matchResult?.let { println(it.value) } // 输出 "a"
??
:匹配前面的字符零次或一次,但尽可能少匹配。例如,对于字符串a
,a??
会匹配空字符串。
val regex = Regex("a??") val str = "a" val matchResult = regex.find(str) matchResult?.let { println(it.value) } // 输出空字符串
{n}?
:匹配前面的字符恰好n
次,这里?
没有实际作用,因为已经明确了匹配次数。{n,}?
:匹配前面的字符至少n
次,但尽可能少匹配。例如,对于字符串aaaa
,a{2,}?
会匹配aa
。
val regex = Regex("a{2,}?") val str = "aaaa" val matchResult = regex.find(str) matchResult?.let { println(it.value) } // 输出 "aa"
{n,m}?
:匹配前面的字符至少n
次,最多m
次,但尽可能少匹配。例如,对于字符串aaaa
,a{2,3}?
会匹配aa
。
val regex = Regex("a{2,3}?") val str = "aaaa" val matchResult = regex.find(str) matchResult?.let { println(it.value) } // 输出 "aa"
边界匹配
^
:匹配字符串的开始位置。例如,^abc
表示以abc
开头的字符串。
val regex = Regex("^abc")
val str1 = "abcdef"
val str2 = "defabc"
println(regex.matches(str1)) // true
println(regex.matches(str2)) // false
$
:匹配字符串的结束位置。例如,abc$
表示以abc
结尾的字符串。
val regex = Regex("abc$")
val str1 = "defabc"
val str2 = "abcdef"
println(regex.matches(str1)) // true
println(regex.matches(str2)) // false
\b
:匹配单词边界。单词边界是指单词字符(\w
)和非单词字符(\W
)之间的位置,或者字符串开始/结束位置与单词字符之间的位置。例如,\bcat\b
只匹配单词cat
,而不匹配category
中的cat
。
val regex = Regex("\\bcat\\b")
val str1 = "cat"
val str2 = "category"
println(regex.containsMatchIn(str1)) // true
println(regex.containsMatchIn(str2)) // false
\B
:匹配非单词边界。例如,\Bcat\B
匹配category
中的cat
,但不匹配单独的cat
单词。
val regex = Regex("\\Bcat\\B")
val str1 = "cat"
val str2 = "category"
println(regex.containsMatchIn(str1)) // false
println(regex.containsMatchIn(str2)) // true
分组
- 捕获组:
使用圆括号
()
定义捕获组。捕获组可以记住匹配的子串,以便后续使用。例如,(abc)
定义了一个捕获组,它会捕获匹配abc
的子串。
val regex = Regex("(abc)")
val str = "abcdef"
val matchResult = regex.find(str)
matchResult?.let {
println(it.groupValues[0]) // 完整匹配的字符串 "abc"
println(it.groupValues[1]) // 第一个捕获组的内容 "abc"
}
可以有多个捕获组,例如 (abc)(def)
,它们按照从左到右的顺序编号,编号从 1 开始。
val regex = Regex("(abc)(def)")
val str = "abcdef"
val matchResult = regex.find(str)
matchResult?.let {
println(it.groupValues[0]) // 完整匹配的字符串 "abcdef"
println(it.groupValues[1]) // 第一个捕获组的内容 "abc"
println(it.groupValues[2]) // 第二个捕获组的内容 "def"
}
- 命名捕获组:
在 Kotlin 中,可以给捕获组命名,使用
(?<name>pattern)
的形式,其中name
是组的名称,pattern
是正则表达式。
val regex = Regex("(?<first>abc)(?<second>def)")
val str = "abcdef"
val matchResult = regex.find(str)
matchResult?.let {
println(it.groupValues[0]) // 完整匹配的字符串 "abcdef"
println(it.groups["first"]?.value) // 第一个命名捕获组的内容 "abc"
println(it.groups["second"]?.value) // 第二个命名捕获组的内容 "def"
}
- 非捕获组:
如果只想分组但不想捕获,可以使用
(?:pattern)
的形式。例如,(?:abc)(def)
中(?:abc)
是一个非捕获组,只有(def)
会被捕获。
val regex = Regex("(?:abc)(def)")
val str = "abcdef"
val matchResult = regex.find(str)
matchResult?.let {
println(it.groupValues[0]) // 完整匹配的字符串 "abcdef"
println(it.groupValues[1]) // 第一个捕获组(这里是 (def))的内容 "def"
}
正则表达式的高级应用
替换操作
replace
方法:Regex
类的replace
方法用于将字符串中匹配正则表达式的部分替换为指定的字符串。
val regex = Regex("abc")
val str = "123abc456"
val newStr = regex.replace(str, "xyz")
println(newStr) // 输出 "123xyz456"
- 使用函数进行替换:
replace
方法还可以接受一个函数作为参数,该函数根据匹配结果生成替换内容。
val regex = Regex("\\d+")
val str = "a12b34c"
val newStr = regex.replace(str) { matchResult ->
(matchResult.value.toInt() * 2).toString()
}
println(newStr) // 输出 "a24b68c"
replaceFirst
方法:replaceFirst
方法只替换字符串中第一个匹配的子串。
val regex = Regex("abc")
val str = "abcabcabc"
val newStr = regex.replaceFirst(str, "xyz")
println(newStr) // 输出 "xyzabcabc"
分割操作
split
方法:Regex
类的split
方法用于根据正则表达式将字符串分割成多个子串。
val regex = Regex(",")
val str = "a,b,c"
val parts = regex.split(str)
println(parts.contentToString()) // 输出 "[a, b, c]"
- 限制分割次数:
split
方法可以接受一个参数指定最大分割次数。
val regex = Regex(",")
val str = "a,b,c,d"
val parts = regex.split(str, 2)
println(parts.contentToString()) // 输出 "[a, b, c,d]"
查找所有匹配
findAll
方法:findAll
方法返回一个MatchResult
的序列,包含字符串中所有匹配的结果。
val regex = Regex("\\d+")
val str = "a12b34c56"
val matches = regex.findAll(str)
matches.forEach {
println(it.value) // 输出 "12", "34", "56"
}
- 获取捕获组信息:
对于包含捕获组的正则表达式,
findAll
返回的MatchResult
可以获取每个捕获组的信息。
val regex = Regex("(\\d+)([a - z])")
val str = "12a34b"
val matches = regex.findAll(str)
matches.forEach {
println(it.groupValues[0]) // 完整匹配的字符串,如 "12a"
println(it.groupValues[1]) // 第一个捕获组的内容,如 "12"
println(it.groupValues[2]) // 第二个捕获组的内容,如 "a"
}
性能优化
- 缓存 Regex 对象:
如果在程序中多次使用同一个正则表达式,应该缓存
Regex
对象,而不是每次都创建新的。
private val regex = Regex("\\d+")
fun processString(str: String) {
val matches = regex.findAll(str)
// 处理匹配结果
}
- 避免不必要的捕获组:
捕获组会增加匹配的开销,因为需要记住匹配的子串。如果不需要捕获某些分组的内容,使用非捕获组
(?:pattern)
。 - 使用合适的量词:
尽量使用精确的量词,避免使用过于贪婪或懒惰的量词,因为它们可能会导致不必要的回溯。例如,如果确定匹配次数,使用
{n}
比*
或+
更高效。
常见应用场景
验证邮箱地址
val emailRegex = Regex("^[A - Za - z0 - 9._%+-]+@[A - Za - z0 - 9.-]+\\.[A - Za - z]{2,}$")
fun isValidEmail(email: String): Boolean {
return emailRegex.matches(email)
}
验证手机号码
val phoneRegex = Regex("^1[3 - 9]\\d{9}$")
fun isValidPhone(phone: String): Boolean {
return phoneRegex.matches(phone)
}
提取 URL
val urlRegex = Regex("(https?|ftp)://([-A - Za - z0 - 9.]+)(/[-A - Za - z0 - 9_\\./]*)?")
fun extractUrls(str: String): List<String> {
return urlRegex.findAll(str).map { it.value }.toList()
}
通过以上对 Kotlin 正则表达式的详细介绍,包括基础使用、语法、高级应用、性能优化和常见场景,相信开发者能够在实际项目中高效地使用正则表达式来处理字符串相关的任务。在实际应用中,需要根据具体需求灵活运用正则表达式的各种特性,以达到最佳的效果。同时,要注意正则表达式的可读性和可维护性,对于复杂的正则表达式,可以添加注释进行说明。