正则表达式入门指南

前言

在开发过程中,处理文本等信息时,常会遇到一些需要复杂筛选或匹配的需求,例如筛选指定类型的日志、筛选符合某种规范的文本、匹配字符串中的某些子串。很多场景下,通过简单的 if-else 或更通用的接口或许能处理这些问题,但筛选的复杂度上升之后,简单的条件判断或是单个接口已疲于应对,此时或许可以考虑正则表达式,通过一串规格化的字符串来匹配所需的文本。

一、简要介绍

正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于描述字符串匹配模式的语言,诞生于 20 世纪 50 年代的数学领域,后被深深嵌入到编程语言、文本编辑器和命令行工具中。

简单来说,正则表达式就是用一串字符来定义一个搜索模式,你可以用它来完成:

  • 验证用户输入是否合法
  • 从海量日志中提取关键信息
  • 批量替换代码中的特定字符

虽然初时看起来像一串无厘头的字符,但掌握基本规则之后,经过合理地应用就能极大地提高开发过程中处理文本的效率。

二、基本规则

学习正则,本质上是在学习如何描述一个字符串集合。以下是构建模式的核心组件。

1)锚点

不消耗字符,只限定位置。

符号 含义
^ 匹配字符串开头
$ 匹配字符串结尾
\b 匹配单词边界

例如 \bcat\b 匹配独立的单词 cat,而不匹配 category 中的 cat。

2)字面字符与元字符

  • 字面字符:直接匹配自身,例如正则 cat 匹配字符串中的 cat。
  • 元字符:有特殊含义的符号,需要转义才能匹配字面意义。

常见元字符:

. * + ? ^ $ { } [ ] ( ) | \

若想匹配 $ 符号本身,需用 \$ 表示。

3)字符类

用方括号 [] 定义一个字符集合,匹配其中任意一个字符。

写法 含义
[aeiou] 匹配任意一个元音字母
[a-z] 匹配小写字母
[0-9] 匹配数字
[^a-z] 取反,匹配非小写字母的字符

预定义字符类:

写法 含义 等价于
\d 匹配一个数字 [0-9]
\w 匹配一个单词字符(字母、数字、下划线) [a-zA-Z0-9_]
\s 匹配一个空白字符(空格、制表符、换行等) —

\D、\W、\S 分别对应上述类的反义,即非数字、非单词字符、非空白字符。

4)量词

控制前面元素的出现次数。

写法 含义
* 零次或多次(贪婪)
+ 一次或多次(贪婪)
? 零次或一次
{n} 恰好 n 次
{n,} 至少 n 次
{n,m} n 到 m 次

例如,\d{3,4} 表示匹配 3 到 4 位数字。

5)分组与捕获

用圆括号 () 进行分组,将若干字符视为一个整体,会同时捕获匹配内容以便后续引用。

例如 (ab)+ 匹配 ab、abab。捕获组可以从左到右按括号顺序编号,用 $1、$2 或 \1、\2(根据工具而异)进行反向引用。

非捕获分组 (?:exp) 只分组不捕获,提高效率。

例如 \b(\w+)\b\s+\1\b 用来匹配若干个空格分割的重复的单词,如 word word、baby baby。

6)分支与转义

| 表示"或"。cat|dog 匹配 cat 或 dog。

\ 用于转义元字符,如 \. 匹配句点 .。

7)贪婪与懒惰

  • 贪婪匹配:正则表达式中包含重复的限定符时,通常的行为是匹配尽可能多的字符。
  • 懒惰匹配:与贪婪匹配相反,通常需要匹配尽可能少的字符。

量词默认是"贪婪"的,会尽可能多地匹配。在量词后加 ? 变为懒惰匹配,匹配尽可能少。

例如 a.*b,它将会匹配最长的以 a 开始、以 b 结束的字符串。如果用它来搜索 aabab 的话,它会匹配整个字符串 aabab。但是此时我们可能需要匹配的是 ab,这样的话就需要用到懒惰匹配来匹配尽可能少的字符。

字符串 <div>hello</div>:使用 <.*> 来贪婪匹配整个字符串;使用 <.*?> 懒惰匹配前后两个 <div>。

三、用途与事例

理论过后,还是需要看看实际场景中的正则如何发挥作用。以下示例基于 Java 语法。

1)表单验证

验证电子邮件地址(简化版):

^[\w.-]+@[\w-]+\.[a-zA-Z]{2,}$

说明:用户名部分允许字母数字、点、下划线、百分号、加号、减号;@ 符号;域名部分允许字母数字和连字符;顶级域名至少 2 个字母。更精确的邮箱验证非常复杂,但这个版本能应对多数日常场景。

2)日志提取

从 Nginx 日志中提取 IP 和访问时间:

String log = "192.168.1.1 - - [10/May/2026:14:30:00 +0000] \"GET /index.html HTTP/1.1\" 200 2326";

String regex = "^(\\S+) .*\\[([^\\]]+)\\].*\"(\\S+) (\\S+).*\" (\\d+)";

Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(log);

匹配结果:

组 内容
$1 IP 地址
$2 时间戳
$3 请求方法(GET)
$4 请求路径
$5 状态码

3)代码重构

假设需要处理一段 JavaScript 代码文本,将其中的 var 声明统一替换为 let,但仅限于变量声明,不触碰字符串或注释中的 var。利用 Java 的 replaceAll 或 Matcher.replaceAll 即可完成。

import java.util.regex.Pattern;

public class CodeRefactor {
    public static void main(String[] args) {
        String jsCode = "var name = 'var user'; var age = 25; /* var placeholder */";

        // 正则:\bvar\b 匹配独立单词 var,\s+ 匹配空格,(\w+) 捕获变量名
        String regex = "\\bvar\\b\\s+(\\w+)";
        String replacement = "let $1";

        String refactored = jsCode.replaceAll(regex, replacement);
        System.out.println(refactored);
        // 输出: let name = 'var user'; let age = 25; /* var placeholder */
    }
}

4)非结构化数据整理

从一段文本中提取所有电话号码(假设格式为 xxx-xxxx-xxxx)。使用 Matcher.find() 可以迭代所有匹配,避免长数字串的误匹配则用单词边界 \b。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;

public class PhoneExtractor {
    public static void main(String[] args) {
        String text = "请联系客服:400-1234-5678,或拨打010-8888-9999,手机138-0000-1234也有效。";

        String regex = "\\b\\d{3}-\\d{4}-\\d{4}\\b";  // 更精确的格式
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);

        List<String> phones = new ArrayList<>();
        while (matcher.find()) {
            phones.add(matcher.group());
        }

        System.out.println("提取到的电话号码: " + phones);
        // 输出: [400-1234-5678, 010-8888-9999, 138-0000-1234]
    }
}

四、问题与误区

正则虽强大,但用不好就是"一次编写,永远调试"的噩梦。以下罗列一些常见的误区与陷阱。

1)把正则当成万能工具

  • 误区:试图用一条正则解决所有情况,比如完美匹配所有电子邮件地址。
  • 纠正:正则适合处理规则明确、非递归的模式。复杂业务逻辑请结合代码判断,而不是写出几十行"面条正则"。比如邮箱验证,正则做表层过滤,再发送确认邮件才是可靠方案。

2)灾难性回溯

当正则引擎面对嵌套量词且匹配失败时,可能尝试指数级路径,导致 CPU 占用飙升。

例:使用 (a+)+b 匹配 aaaaaaaaac,会导致大量回溯。

避免方法:

  • 尽量使用非捕获组和占有量词(如果引擎支持)。
  • 写正则时避免 (x+)+ 或类似的嵌套贪婪结构。
  • 使用前考虑利用特定工具或网站测试正则性能。

3)忘记转义与上下文

  • 点号 . 匹配任意字符,若想匹配 IP 地址中的点,必须用 \.,否则 192.168.1.1 中的点可能匹配任意字符,造成误判。
  • 连字符 - 在字符类中间表示范围,若想匹配字面 -,需放在字符类的开头或结尾,如 [-a-z] 或 [a-z-]。

4)多行与单行模式的混淆

  • 多行模式(m 标志):^ 和 $ 匹配每一行的开头结尾。为了匹配换行后文本需开启多行模式。
  • 单行模式(s 标志):点号 . 匹配包括换行在内的所有字符。

初次接触正则时,有可能在未开启相应模式时认为 ^.*$ 不跨行或点号不匹配换行,根源是没有确认这些标志。

5)忽略性能与可读性

复杂正则不易读,时间长了之后较难回溯含义。

解决方案:

  • 添加注释(若引擎支持 x 模式),或在代码中拆分成多段变量。
  • 优先选择简单、直接的表达。例如用 \d{3}-\d{4}-\d{4} 而不是企图用一堆环视来限制号码段,除非确实有严格业务需求。

五、结语

正则表达式是一门"写一次、读万次"的手艺。它抽象、精练,能将开发过程从烦琐的字符串操作中解放出来;同时又固执、深奥,稍不留神就会引入隐蔽的 bug。

对于入门而言,首先掌握基础规则,再在真实项目中学以致用,不必试图背下所有语法,善用在线工具(regex101、RegExr 等)即时测试,方可熟能生巧。

正则不会淘汰,因为文本永远是最通用的数据格式之一,善用正则表达式,能让代码更简洁,也能让文本处理的思维变得更加"模式化"和精准。

附录:常用的实用正则表达式整理

用途 正则表达式
只能输入数字 ^[0-9]*$
只能输入 n 位的数字 ^\d{n}$
只能输入至少 n 位的数字 ^\d{n,}$
只能输入 m~n 位的数字 ^\d{m,n}$
只能输入零和非零开头的数字 `^(0
只能输入有两位小数的正实数 ^[0-9]+(\.[0-9]{2})?$
只能输入有 1~3 位小数的正实数 ^[0-9]+(\.[0-9]{1,3})?$
只能输入非零的正整数 ^\+?[1-9][0-9]*$
只能输入非零的负整数 ^-[1-9][0-9]*$
只能输入长度为 3 的字符 ^.{3}$
只能输入由 26 个英文字母组成的字符串 ^[A-Za-z]+$
只能输入由 26 个大写英文字母组成的字符串 ^[A-Z]+$
只能输入由 26 个小写英文字母组成的字符串 ^[a-z]+$
只能输入由数字和 26 个英文字母组成的字符串 ^[A-Za-z0-9]+$
只能输入由数字、26 个英文字母或者下划线组成的字符串 ^\w+$
验证用户密码 ^[a-zA-Z]\w{5,17}$
验证是否含有 ^%&',;=?$" 等字符 [^%&',;=?$\x22]+
只能输入汉字 ^[\u4e00-\u9fa5]{0,}$
验证 Email 地址 /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/
验证 InternetURL ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
验证电话号码 ^(\(\d{3,4}-|\d{3,4}-)?\d{7,8}$
验证身份证号(15 位或 18 位数字) ^\d{15}|\d{18}$
验证一年的 12 个月 ^(0?[1-9]|1[0-2])$
验证一个月的 31 天 ^((0?[1-9])|((1|2)[0-9])|30|31)$
posted @ 2026-10-14 19:37  浅梦  阅读(3)  评论(0)    收藏  举报