Scala 正则表达式(超详细)

Scala 正则表达式入门:从零开始掌握文本匹配的艺术

在日常开发中,我们常常需要从一段文本中提取特定信息,比如验证邮箱格式、提取电话号码、清洗日志数据等。这类任务如果用传统字符串遍历处理,代码会冗长且容易出错。而 Scala 正则表达式,正是解决这类问题的利器。

想象一下,你有一堆乱序的用户注册信息,需要从中找出所有有效的邮箱地址。如果没有正则表达式,你得逐字符判断是否包含 @.com 等,逻辑复杂又难维护。而有了 Scala 正则表达式,只需一行代码就能精准匹配,高效又优雅。

Scala 作为一门融合函数式与面向对象特性的语言,其正则表达式能力非常强大,且与集合操作无缝集成。今天我们就来系统学习 Scala 正则表达式的核心用法,让你在处理文本时得心应手。


什么是正则表达式?它如何工作?

正则表达式(Regular Expression)是一种用于描述字符串模式的语法。你可以把它理解为“文本的指纹模板”——你告诉系统:“我要找的是符合这种结构的字符串”。

比如,想匹配一个常见的邮箱格式,你可以写:^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

这个表达式虽然看起来复杂,但每一部分都有明确含义:

  • ^ 表示字符串开始
  • [a-zA-Z0-9._%+-]+ 表示用户名部分,可以包含字母、数字、点、下划线等
  • @ 是必须存在的符号
  • [a-zA-Z0-9.-]+ 是域名部分
  • \. 匹配实际的点号(因为点在正则中是特殊字符)
  • [a-zA-Z]{2,} 表示顶级域名至少两个字母
  • $ 表示字符串结束

在 Scala 中,你可以通过 """ 字符串字面量来定义正则表达式,避免转义问题。


创建与编译正则表达式

在 Scala 中,使用 scala.util.matching.Regex 类来表示正则表达式。创建方式非常直观:

val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r

这里的关键是 .r 方法,它把字符串转换为一个 Regex 对象。这个对象可以被反复使用,性能优于每次重新编译。

注意:使用三重引号 """ 能避免在字符串中写反斜杠时的转义问题,比如 \. 无需写成 \\.

一旦编译完成,这个 emailRegex 就像一个“匹配引擎”,你可以用它去检查任何字符串是否符合该模式。


使用 match 检查是否匹配

最基础的用法是判断一个字符串是否完全匹配正则表达式。使用 matches 方法即可:

val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r

val testEmail = "user@example.com"

// 判断是否匹配
if (emailRegex.matches(testEmail)) {
  println("邮箱格式正确")
} else {
  println("邮箱格式错误")
}

这段代码会输出:邮箱格式正确

matches 是严格匹配,要求整个字符串都符合模式。如果你只想检查某部分是否包含匹配项,就要用 findFirstInfindAllIn


提取匹配的子字符串

很多时候,我们不仅想知道“有没有匹配”,更想知道“匹配了什么”。这时就需要用到 findFirstInfindAllIn

使用 findFirstIn 提取第一个匹配项

val phoneRegex = """(\d{3})-(\d{4})-(\d{4})""".r

val text = "联系我:010-1234-5678 或 021-8765-4321"

// 查找第一个匹配的电话号码
val matchResult = phoneRegex.findFirstIn(text)

matchResult match {
  case Some(matched) => println(s"找到匹配:$matched")
  case None => println("未找到匹配")
}

输出:找到匹配:010-1234-5678

findFirstIn 返回的是 Option[String],因为可能没有匹配项。使用 match 模式匹配处理 SomeNone 是 Scala 的惯用写法。

使用 findAllIn 提取所有匹配项

val phoneRegex = """(\d{3})-(\d{4})-(\d{4})""".r

val text = "联系我:010-1234-5678 或 021-8765-4321"

// 提取所有匹配的电话号码
val allMatches = phoneRegex.findAllIn(text)

// 将结果转换为列表
val phoneList = allMatches.toList

println(s"共找到 ${phoneList.length} 个电话号码:")
phoneList.foreach(println)

输出:

共找到 2 个电话号码:
010-1234-5678
021-8765-4321

findAllIn 返回的是 MatchIterator,可以迭代处理。使用 toList 可以将其转为 List,便于后续操作。


分组捕获:提取匹配中的特定部分

正则表达式中用括号 () 表示“分组”。每个括号内的内容可以单独提取出来,这就是“捕获组”。

比如上面的电话号码正则:(\d{3})-(\d{4})-(\d{4}) 有三个分组:

  • 第一组:区号(3 位数字)
  • 第二组:前四位
  • 第三组:后四位

我们可以通过 group 方法获取这些内容:

val phoneRegex = """(\d{3})-(\d{4})-(\d{4})""".r

val text = "联系我:010-1234-5678"

// 使用 findFirstIn 获取匹配结果
val matchResult = phoneRegex.findFirstIn(text)

matchResult match {
  case Some(matched) =>
    val groups = matched.group
    // group(0) 是完整匹配,group(1) 是第一组,以此类推
    println(s"完整匹配:${groups(0)}")
    println(s"区号:${groups(1)}")
    println(s"前四位:${groups(2)}")
    println(s"后四位:${groups(3)}")
  case None =>
    println("未找到匹配")
}

输出:

完整匹配:010-1234-5678
区号:010
前四位:1234
后四位:5678

注意:group 返回的是 String 类型的数组,索引从 1 开始(group(0) 是完整匹配,不是分组)


替换文本:用正则表达式修改字符串

正则表达式不仅可以“找”,还能“改”。Scala 提供了 replaceAllInreplaceFirstIn 方法用于替换。

替换所有匹配项

val urlRegex = """https?://[^\s]+""".r

val content = "请访问 https://www.example.com 和 http://test.org"

// 将所有 URL 替换为 [链接] 标记
val cleanedContent = urlRegex.replaceAllIn(content, "[链接]")

println(cleanedContent)

输出:请访问 [链接] 和 [链接]

replaceAllIn 会替换所有匹配项,返回新字符串。

替换第一个匹配项

val phoneRegex = """(\d{3})-(\d{4})-(\d{4})""".r

val text = "电话:010-1234-5678 或 021-8765-4321"

// 只替换第一个电话号码
val firstReplaced = phoneRegex.replaceFirstIn(text, "XXX-XXXX-XXXX")

println(firstReplaced)

输出:电话:XXX-XXXX-XXXX 或 021-8765-4321


常见应用场景与实战案例

验证用户输入

val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r

def isValidEmail(email: String): Boolean = {
  emailRegex.matches(email)
}

// 测试
println(isValidEmail("user@example.com")) // true
println(isValidEmail("invalid.email"))    // false

提取日志中的时间戳

假设日志格式为:[2024-05-01 14:23:05] 用户登录成功

val logTimeRegex = """\[(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\]""".r

val logLine = "[2024-05-01 14:23:05] 用户登录成功"

val timeMatch = logTimeRegex.findFirstIn(logLine)

timeMatch match {
  case Some(matched) =>
    val Array(year, month, day, hour, minute, second) = matched.group
    println(s"时间:$year年$month月$day日 $hour:$minute:$second")
  case None => println("未找到时间戳")
}

输出:时间:2024年05月01日 14:23:05


小结与建议

Scala 正则表达式是一套强大而灵活的文本处理工具。它不仅能帮你快速验证数据格式,还能高效提取关键信息、批量替换内容。掌握它,意味着你拥有了处理非结构化文本的“超级能力”。

在实际项目中,建议:

  • 对重复使用的正则表达式,提前编译为 Regex 对象,避免重复解析
  • 使用分组捕获来提取结构化信息,而不是手动切割字符串
  • 复杂正则尽量添加注释,或拆分为多个简单表达式
  • 注意性能:过于复杂的正则可能导致匹配效率下降

正则表达式像一把瑞士军刀,用得好能事半功倍,用得不好反而制造麻烦。多练习、多调试,你一定能熟练驾驭 Scala 正则表达式。