一、基本概念
正则表达式(Regular Expression)是一种强大的文本处理工具,用于匹配、查找、替换字符串中的特定模式。它通过一系列预定义的语法规则,描述字符串的结构特征,广泛应用于数据验证、文本解析、日志分析、搜索引擎等领域。
二、基础语法(核心元字符与规则)
正则表达式的语法由「普通字符」和「元字符」组成,元字符是具有特殊含义的符号,掌握它们是使用正则的基础。
1. 普通字符
- 直接匹配自身,无特殊含义(如
a、b、1、 空格等)。 - 示例:
abc匹配字符串"abc",123匹配"123"。
2. 核心元字符(按功能分类)
| 元字符 | 功能说明 | 示例 |
|---|---|---|
. |
匹配任意单个字符(默认不匹配换行符 \n,部分语言可通过修饰符改变)。 |
a.b 匹配 "axb"、"a1b"、"a#b"(不匹配 "a\nb")。 |
^ |
匹配字符串开头(多行模式下匹配每行开头)。 | ^hello 匹配 "hello world"(开头是 hello),不匹配 "xhello"。 |
$ |
匹配字符串结尾(多行模式下匹配每行结尾)。 | world$ 匹配 "hello world"(结尾是 world),不匹配 "worldx"。 |
* |
匹配前面的元素 0 次或多次(贪婪匹配:尽可能多匹配)。 | ab* 匹配 "a"、"ab"、"abb"、"abbb"。 |
+ |
匹配前面的元素 1 次或多次(贪婪匹配)。 | ab+ 匹配 "ab"、"abb"(不匹配 "a")。 |
? |
匹配前面的元素 0 次或 1 次(可选匹配)。 | ab? 匹配 "a"、"ab"(不匹配 "abb")。 |
{n} |
匹配前面的元素 恰好 n 次(n 为非负整数)。 | a{3} 匹配 "aaa"(不匹配 "aa"、"aaaa")。 |
{n,} |
匹配前面的元素 至少 n 次(贪婪匹配)。 | a{2,} 匹配 "aa"、"aaa"、"aaaa"。 |
{n,m} |
匹配前面的元素 n 到 m 次(n ≤ m,贪婪匹配)。 | a{2,3} 匹配 "aa"、"aaa"(不匹配 "a"、"aaaa")。 |
| ` | ` | 逻辑「或」,匹配左边或右边的模式(优先级最低,需用括号提升优先级)。 |
() |
分组:将多个字符视为一个整体,同时捕获匹配结果(可通过反向引用使用)。 | (ab)+ 匹配 "ab"、"abab";(\w+)\s+(\w+) 捕获两个单词(如 "hello world")。 |
\ |
转义字符:取消元字符的特殊含义,或表示预定义字符类(如 \d、\s)。 |
a\.b 匹配 "a.b"(. 作为普通字符);\* 匹配 "*"。 |
[] |
字符类:匹配括号内任意一个字符(支持范围表示,^ 表示否定)。 | [abc] 匹配 "a"、"b"、"c";[^abc] 匹配非 a/b/c 的字符。 |
[-] |
字符类内的范围符:表示连续字符集(如字母、数字范围)。 | [a-z] 匹配小写字母;[0-9] 匹配数字;[a-zA-Z0-9] 匹配字母数字。 |
3. 预定义字符类(简化写法)
为了简化常见的字符匹配,正则提供了预定义的字符类(部分需结合修饰符使用):
| 预定义类 | 等价于 | 功能说明 |
|---|---|---|
\d |
[0-9] |
匹配任意数字(digit)。 |
\D |
[^0-9] |
匹配任意非数字。 |
\w |
[a-zA-Z0-9_] |
匹配字母、数字、下划线(word 字符,注意不包含空格和特殊字符)。 |
\W |
[^a-zA-Z0-9_] |
匹配非字母、数字、下划线。 |
\s |
[ \t\n\r\f\v] |
匹配任意空白字符(space:空格、制表符 \t、换行符 \n 等)。 |
\S |
[^ \t\n\r\f\v] |
匹配任意非空白字符。 |
\b |
- | 匹配单词边界(单词与非单词的分隔处,如 "hello world" 中的空格两侧)。 |
\B |
- | 匹配非单词边界(如 "helloworld" 中两个单词的连接处)。 |
\n |
- | 匹配换行符(部分语言支持 \r\n 匹配 Windows 换行)。 |
\t |
- | 匹配制表符。 |
示例:
\d{3}-\d{4}匹配"123-4567"(3 位数字 + 横杠 + 4 位数字)。\w+@\w+\.\w+匹配简单邮箱(如"test123@example.com")。\bhello\b匹配独立的单词"hello"(不匹配"helloworld"或"xhello")。
三、核心特性(进阶用法)
掌握基础语法后,需理解正则的核心特性(贪婪 / 非贪婪、分组 / 捕获、断言等),才能应对复杂场景。
1. 贪婪匹配与非贪婪匹配
默认情况下,正则的量词(*、+、?、{n,m})是「贪婪的」------ 尽可能匹配更多字符。在量词后加 ? 可变为「非贪婪」------ 尽可能匹配更少字符。
| 模式 | 类型 | 示例(匹配字符串 "aabbaabcc") |
|---|---|---|
a.*b |
贪婪 | 匹配 "aabbaab"(从第一个 a 到最后一个 b)。 |
a.*?b |
非贪婪 | 匹配 "aab"(从第一个 a 到最近的 b)。 |
a.+c |
贪婪 | 匹配 "aabbaabcc"(从第一个 a 到最后一个 c)。 |
a.+?c |
非贪婪 | 匹配 "aabbaabc"(从第一个 a 到最近的 c)。 |
2. 分组与捕获
() 不仅能将模式分组(视为整体),还能「捕获」匹配到的内容,后续可通过「反向引用」或编程接口获取。
(1)普通捕获组
- 语法:
(pattern) - 捕获的内容按分组顺序编号(从 1 开始),可通过
\1、\2...(正则内)或编程变量(如 Python 的group(1))引用。
示例:
- 匹配重复单词:
(\w+)\s+\1解释:(\w+)捕获第一个单词,\s+匹配空格,\1引用第一个捕获的单词,可匹配"hello hello"、"test test"。 - 提取日期中的年、月、日:
(\d{4})-(\d{2})-(\d{2})匹配"2025-11-21"后,捕获组 1 ="2025",组 2 ="11",组 3 ="21"。
(2)非捕获组
若只需分组(不捕获结果),用 (?:pattern),可提升性能(避免不必要的内存占用)。
示例:
(?:ab)+匹配"ab"、"abab"(不捕获"ab",仅作为整体匹配)。
(3)命名捕获组
为捕获组命名,便于后续引用(避免按编号混淆),语法:(?<name>pattern)(部分语言支持,如 Python、JavaScript、Java)。
示例:
- 提取日期:
(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})匹配后可通过名称引用(如 Python 的group("year")),更直观。
3. 零宽断言(Lookaround)
零宽断言是「匹配位置」的模式,不消耗字符(仅判断当前位置前后是否满足条件),分为「先行断言」和「后行断言」。
(1)先行断言(Lookahead)
判断当前位置后面是否满足条件,语法:
- 正向先行:
(?=pattern)→ 后面必须是 pattern。 - 负向先行:
(?!pattern)→ 后面必须不是 pattern。
示例:
- 匹配「后面是数字的字母」:
[a-zA-Z](?=\d)匹配"a1"中的"a"、"b23"中的"b"(后面必须跟数字)。 - 匹配「后面不是数字的字母」:
[a-zA-Z](?!\d)匹配"ab"中的"a"、"c#"中的"c"(后面不是数字)。 - 验证密码(必须包含数字):
^(?=.*\d).{6,12}$解释:^开头,(?=.*\d)后面必须有至少一个数字,.{6,12}匹配 6-12 个任意字符,$结尾。
(2)后行断言(Lookbehind)
判断当前位置前面是否满足条件,语法:
- 正向后行:
(?<=pattern)→ 前面必须是 pattern。 - 负向后行:
(?<!pattern)→ 前面必须不是 pattern。
示例:
- 匹配「前面是数字的字母」:
(?<=\d)[a-zA-Z]匹配"1a"中的"a"、"23b"中的"b"(前面是数字)。 - 匹配「前面不是数字的字母」:
(?<!\d)[a-zA-Z]匹配"xa"中的"a"、"#b"中的"b"(前面不是数字)。 - 提取「 后面的数字」:`(?<=\$)\d+`匹配`"123"
中的"123"、"price: 45"`中的`"45"\`(前面是 )。
4. 模式修饰符(Flags)
修饰符用于改变正则的匹配行为(如忽略大小写、多行匹配等),不同语言的表示方式不同(如 Python 用 re.IGNORECASE,JavaScript 用 /pattern/gi)。
常见修饰符:
| 修饰符 | 作用说明 |
|---|---|
i |
忽略大小写(case-insensitive):a 可匹配 A,B 可匹配 b。 |
g |
全局匹配(global):找到所有匹配项,而非第一个(部分语言默认全局)。 |
m |
多行模式(multi-line):^ 匹配每行开头,$ 匹配每行结尾(默认仅匹配整体开头 / 结尾)。 |
s |
单行模式(single-line):. 匹配所有字符(包括换行符 \n,默认不匹配)。 |
u |
Unicode 模式:支持 Unicode 字符(如 \p{L} 匹配任意语言的字母)。 |
x |
忽略空格和注释(extended):便于编写复杂正则(用 # 写注释)。 |
示例:
- 忽略大小写匹配:
/hello/i匹配"Hello"、"HELLO"、"hello"。 - 多行模式匹配:
/^hello/m匹配"hello\nworld\nhello"中的两个"hello"(每行开头)。 - 单行模式匹配:
/a.b/s匹配"a\nb"(.匹配换行符)。
四、常见应用场景(实战示例)
正则的核心价值是解决文本处理问题,以下是高频场景的实战示例:
1. 数据验证
示例 1:验证邮箱地址
cs
using System;
using System.Text.RegularExpressions;
public class EmailValidator
{
public static bool IsValidEmail(string email)
{
// 正则表达式:^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
string pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
return Regex.IsMatch(email, pattern);
}
public static void Main()
{
string[] emails = {
"test@example.com",
"user.name+tag@example.co.uk",
"invalid-email",
"another.invalid@.com"
};
foreach (var email in emails)
{
Console.WriteLine($"{email}: {IsValidEmail(email)}");
}
}
}
输出:
cs
test@example.com: True
user.name+tag@example.co.uk: True
invalid-email: False
another.invalid@.com: False
示例 2:验证手机号(中国大陆)
cs
public class PhoneValidator
{
public static bool IsValidPhone(string phone)
{
// 正则表达式:^1[3-9]\d{9}$
string pattern = @"^1[3-9]\d{9}$";
return Regex.IsMatch(phone, pattern);
}
public static void Main()
{
string[] phones = {
"13812345678",
"19987654321",
"1234567890",
"138123456789"
};
foreach (var phone in phones)
{
Console.WriteLine($"{phone}: {IsValidPhone(phone)}");
}
}
}
2. 文本提取
示例 3:从 HTML 中提取链接
cs
using System;
using System.Text.RegularExpressions;
class LinkExtractor
{
static void Main()
{
string html = @"
<a href=""https://www.example.com"">Example</a>
<a href=""https://www.google.com"">Google</a>
<img src=""image.jpg"">
";
// 正则表达式:<a\s+href=""([^""]*)""
string pattern = @"<a\s+href=""([^""]*)""";
MatchCollection matches = Regex.Matches(html, pattern);
foreach (Match match in matches)
{
Console.WriteLine(match.Groups[1].Value);
}
}
}
输出:
cs
https://www.example.com
https://www.google.com
示例 4:提取字符串中的所有数字
cs
class NumberExtractor
{
static void Main()
{
string input = "订单号:20230518001,金额:99.9元,数量:10";
string pattern = @"\d+\.?\d*"; // 匹配整数或小数
MatchCollection matches = Regex.Matches(input, pattern);
foreach (Match match in matches)
{
Console.WriteLine(match.Value);
}
}
}
输出:
cs
20230518001
99.9
10
3. 文本替换与格式化
示例 5:替换字符串中的敏感信息
cs
class TextReplacer
{
static void Main()
{
string input = "手机号:15112347676,身份证号:410123194910011234";
// 替换手机号中间4位为****
string phonePattern = @"(1[3-9])\d{4}(\d{4})";
string result = Regex.Replace(input, phonePattern, "$1****$2");
// 替换身份证号中间8位为********
string idPattern = @"(^\d{6}|\d{8})(\d{4}$)";
result = Regex.Replace(result, idPattern, "$1********$2");
Console.WriteLine(result);
}
}
输出:
cs
手机号:151****7676,身份证号:410123********1234
示例 6:格式化日期(MM/DD/YYYY → YYYY-MM-DD)
cs
class DateFormatter
{
static void Main()
{
string input = "Today is 11/21/2025";
string pattern = @"\b(\d{2})/(\d{2})/(\d{4})\b";
// 替换为 YYYY-MM-DD
string result = Regex.Replace(input, pattern, "$3-$1-$2");
Console.WriteLine(result);
}
}
输出:
cs
Today is 2025-11-21
4. 高级应用:正则表达式分组与命名捕获
示例 7:解析日志文件
假设日志格式为:[2025-11-21 14:30:00] [INFO] User 'admin' logged in.
cs
class LogParser
{
static void Main()
{
string log = "[2025-11-21 14:30:00] [INFO] User 'admin' logged in.";
string pattern = @"\[(.*?)\] \[(.*?)\] (.*)";
Match match = Regex.Match(log, pattern);
if (match.Success)
{
string timestamp = match.Groups[1].Value;
string level = match.Groups[2].Value;
string message = match.Groups[3].Value;
Console.WriteLine($"时间:{timestamp}\n级别:{level}\n内容:{message}");
}
}
}
输出:
cs
时间:2025-11-21 14:30:00
级别:INFO
内容:User 'admin' logged in.
示例 8:使用命名捕获组
cs
class NamedGroupExample
{
static void Main()
{
string input = "Name: John, Age: 30, Email: john@example.com";
string pattern = @"Name: (?<name>\w+), Age: (?<age>\d+), Email: (?<email>[^,]+)";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine($"姓名:{match.Groups["name"].Value}");
Console.WriteLine($"年龄:{match.Groups["age"].Value}");
Console.WriteLine($"邮箱:{match.Groups["email"].Value}");
}
}
}
五、注意事项与性能优化
1. 注意事项
- 转义问题 :不同语言中,正则的转义规则不同(如 Python 字符串中
\需转义为\\,JavaScript 中直接写\)。 - 部分匹配 vs 完全匹配 :验证格式时必须加
^和$(如手机号1[3-9]\d{9}会匹配"a13812345678b",加^$才会完全匹配)。 - 换行符处理 :默认
.不匹配\n,需用(?s)修饰符或[\s\S](匹配所有字符)。 - Unicode 支持 :处理多语言文本时,需开启 Unicode 模式(如
\p{L}匹配任意语言字母,而非仅[a-zA-Z])。
2. 性能优化
复杂正则可能导致回溯过多,影响性能,需注意:
- 避免嵌套量词(如
(a*)*),容易引发回溯爆炸。 - 用具体字符类代替
.(如匹配数字用\d而非.,减少不必要的匹配)。 - 优先使用非贪婪匹配(
.*?)避免过度匹配,但不要滥用(非贪婪也可能增加回溯)。 - 用原子组
(?>pattern)避免回溯(原子组匹配后不可回溯,适合确定的模式)。 - 拆分复杂正则(如长正则拆分为多个简单正则,分步处理)。
六、常见正则表达式示例(直接复用)
1. 验证类
| 场景 | 正则表达式(C# 字符串需转义,或用 @ 逐字字符串) |
| 手机号(中国大陆) | ^1[3-9]\d{9} |
| 邮箱 | \^\[a-zA-Z0-9._%+-\]+@\[a-zA-Z0-9.-\]+\\.\[a-zA-Z\]{2,} |
| 身份证号(18 位) | ^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}([0-9]|X|x) |
| URL(http/https) | \^https?://\[\^\\s\]+ |
| 日期(yyyy-MM-dd) | ^[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ |
| 密码(8-20 位,含大小写字母、数字、特殊字符) | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,20}$ |
|---|
2. 提取类
| 场景 | 正则表达式 |
| 提取所有数字 | \d+ |
| 提取所有链接 | https?://[^\s]+ |
| 提取 HTML 标签内容(如 <title>xxx</title>) | <title>(.*?)</title> |
| 提取邮箱地址 | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} |
|---|
3. 替换类
| 场景 | 正则表达式 |
| 去除所有空格 | \s+ |
| 格式化手机号(13812345678 → 138-1234-5678) | (\d{3})(\d{4})(\d{4}) |
| 替换 HTML 标签(去除所有标签) | <[^>]+> |
| 敏感词替换("垃圾""废物" 替换为 "***") | (垃圾|废物) |
|---|
希望对大家有所帮助。感谢大家的关注和点赞。