一、前言
正则表达式(Regular Expression,简称 Regex)是一种用于匹配、查找、替换字符串的强大工具,广泛应用于数据验证、日志分析、文本处理等场景。无论是前端表单验证、后端数据清洗,还是爬虫数据提取,正则表达式都能大幅提升开发效率。本文将避开复杂的理论推导,聚焦实用语法 + 场景化案例,帮助开发者快速掌握正则表达式的核心用法。
二、正则表达式基础语法
2.1 核心元字符(必记)
元字符 | 作用说明 | 示例 |
. | 匹配任意单个字符(除换行符\n) | a.b 匹配 acb、aab,不匹配 abbc |
* | 匹配前面的字符 0 次或多次 | ab* 匹配 a、ab、abb、abbb |
+ | 匹配前面的字符 1 次或多次 | ab+ 匹配 ab、abb,不匹配 a |
? | 匹配前面的字符 0 次或 1 次(非贪婪匹配) | ab? 匹配 a、ab,不匹配 abb |
^ | 匹配字符串开头 | ^abc 匹配 abc123,不匹配 xabc |
$ | 匹配字符串结尾 | abc$ 匹配 123abc,不匹配 abcx |
[] | 字符集:匹配其中任意一个字符 | [a-zA-Z] 匹配任意大小写字母 |
[^] | 否定字符集:匹配不在其中的字符 | [^0-9] 匹配非数字字符 |
() | 分组:将多个字符视为一个整体,可捕获结果 | (ab)+ 匹配 ab、abab |
| | 逻辑或:匹配左右任意一个表达式 | a|b 匹配 a 或 b |
\ | 转义字符:匹配元字符本身 | a\.b 匹配 a.b,不匹配 acb |
2.2 常用预定义字符集(简化书写)
预定义字符 | 等价写法 | 作用说明 |
\d | [0-9] | 匹配数字 |
\D | [^0-9] | 匹配非数字 |
\w | [a-zA-Z0-9_] | 匹配字母、数字、下划线 |
\W | [^a-zA-Z0-9_] | 匹配非字母、数字、下划线 |
\s | [ \t\n\r\f] | 匹配空白字符(空格、制表符、换行符等) |
\S | [^ \t\n\r\f] | 匹配非空白字符 |
2.3 量词(指定匹配次数)
量词 | 作用说明 | 示例 |
{n} | 匹配前面的字符恰好 n 次 | a{3} 匹配 aaa,不匹配 aa |
{n,} | 匹配前面的字符至少 n 次 | a{2,} 匹配 aa、aaa、aaaa |
{n,m} | 匹配前面的字符 n 到 m 次 | a{2,3} 匹配 aa、aaa,不匹配 a |
三、实战场景:正则表达式的核心应用
3.1 场景 1:数据验证(最常用)
数据验证是正则表达式的经典场景,如验证手机号、邮箱、身份证号等,确保输入数据格式合法。
3.1.1 验证手机号(中国大陆)
- 规则:11 位数字,以 13/14/15/17/18/19 开头
- 正则表达式:^1[345789]\d{9}$
Java 实现:
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexDemo {
// 手机号正则表达式
private static final String PHONE_REGEX = "^1[345789]\\d{9}$";
private static final Pattern PHONE_PATTERN = Pattern.compile(PHONE_REGEX);
public static boolean validatePhone(String phone) {
if (phone == null || phone.isEmpty()) {
return false;
}
Matcher matcher = PHONE_PATTERN.matcher(phone);
return matcher.matches();
}
public static void main(String[] args) {
System.out.println(validatePhone("13812345678")); // true
System.out.println(validatePhone("12345678901")); // false(开头不是13/14等)
System.out.println(validatePhone("1381234567")); // false(不足11位)
}
}
Python 实现:
import re
def validate_phone(phone):
phone_regex = r"^1[345789]\d{9}$"
return re.fullmatch(phone_regex, phone) is not None
# 测试
print(validate_phone("13812345678")) # True
print(validate_phone("12345678901")) # False
print(validate_phone("1381234567")) # False
3.1.2 验证邮箱
- 规则:用户名 @域名(用户名可包含字母、数字、下划线、点号;域名至少包含一个点)
- 正则表达式:^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$
Python 实现:
def validate_email(email):
email_regex = r"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$"
return re.fullmatch(email_regex, email) is not None
print(validate_email("test123@qq.com")) # True
print(validate_email("test.name_123@gmail.com")) # True
print(validate_email("test@.com")) # False(域名格式错误)
3.2 场景 2:字符串提取(从文本中提取目标信息)
从复杂文本中提取指定格式的信息(如提取日志中的 IP 地址、提取字符串中的数字)。
3.2.1 提取文本中的所有 IP 地址(IPv4)
- IPv4 规则:4 组 0-255 的数字,以点号分隔(需处理 0-255 的范围限制)
- 正则表达式:\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
Java 实现:
public class RegexExtract {
private static final String IP_REGEX = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b";
private static final Pattern IP_PATTERN = Pattern.compile(IP_REGEX);
public static List> extractIp(String text) {
List ips = new ArrayList
Matcher matcher = IP_PATTERN.matcher(text);
while (matcher.find()) { // 循环查找所有匹配的IP
ips.add(matcher.group());
}
return ips;
}
public static void main(String[] args) {
String log = "用户登录日志:IP=192.168.1.1,时间=2024-05-20;IP=255.255.255.255,时间=2024-05-21;无效IP=256.0.0.1";
ListIp(log);
System.out.println(ips); // 输出:[192.168.1.1, 255.255.255.255]
}
}
3.2.2 提取字符串中的所有数字(包括整数和小数)
- 正则表达式:-?\d+(?:\.\d+)?(-?匹配负号,\d+匹配整数部分,(?:\.\d+)?匹配小数部分)
Python 实现:
def extract_numbers(text):
number_regex = r"-?\d+(?:\.\d+)?"
return re.findall(number_regex, text)
text = "商品价格:99元,折扣价:89.9元,库存:-10(缺货),折扣率:0.85"
numbers = extract_numbers(text)
print(numbers) # 输出:['99', '89.9', '-10', '0.85']
3.3 场景 3:字符串替换(批量修改文本)
批量替换文本中的指定内容(如替换特殊字符、格式化日期、脱敏手机号)。
3.3.1 手机号脱敏(中间 4 位替换为 *)
- 需求:将手机号13812345678替换为138****5678
- 正则表达式:(\d{3})\d{4}(\d{4})(分组捕获前 3 位和后 4 位)
Java 实现:
public class RegexReplace {
public static String maskPhone(String phone) {
String phoneRegex = "(\\d{3})\\d{4}(\\d{4})";
// 替换为:$1****$2($1表示第一个分组,$2表示第二个分组)
return phone.replaceAll(phoneRegex, "$1****$2");
}
public static void main(String[] args) {
System.out.println(maskPhone("13812345678")); // 输出:138****5678
}
}
3.3.2 清除文本中的所有空白字符
- 正则表达式:\s+(匹配一个或多个空白字符)
Python 实现:
def remove_whitespace(text):
return re.sub(r"\s+", "", text)
text = " 正则表达式 实战 指南 \n 2024-05-20 "
result = remove_whitespace(text)
print(result) # 输出:正则表达式实战指南2024-05-20
四、进阶技巧:提升正则表达式效率
4.1 分组与捕获
- 捕获组:用()包裹的表达式会被捕获,可通过$n(Java)或\n(Python)引用捕获结果(如 3.3.1 中的手机号脱敏);
- 非捕获组:用(?:)包裹的表达式仅用于分组,不捕获结果,可提升性能(如 IP 提取中的(?:25[0-5]...))。
4.2 贪婪匹配与非贪婪匹配
- 贪婪匹配(默认):尽可能匹配更多字符(如a.*b匹配aabbaab中的aabbaab);
- 非贪婪匹配:在量词后加?,尽可能匹配更少字符(如a.*?b匹配aabbaab中的aab和aab)。
示例(Python):
text = "aaabbbccc"
# 贪婪匹配:.* 匹配所有字符,直到最后一个c
greedy = re.findall(r"a.*c", text)
print(greedy) # 输出:['aaabbbccc']
# 非贪婪匹配:.*? 匹配到第一个c为止
non_greedy = re.findall(r"a.*?c", text)
print(non_greedy) # 输出:['aaabbbccc']?不,实际输出:['aaabbbccc']?不对,修正示例:
text = "aaacbbbc"
greedy = re.findall(r"a.*c", text) # 匹配整个字符串:['aaacbbbc']
non_greedy = re.findall(r"a.*?c", text) # 匹配到第一个c:['aaac']
4.3 正向预查与反向预查(零宽断言)
零宽断言仅匹配位置,不消耗字符,常用于 “匹配某个字符前后的内容”。
断言类型 | 语法 | 作用说明 | 示例 |
正向先行断言 | (?=exp) | 匹配 exp 前面的位置 | a(?=b) 匹配ab中的a,不匹配ac中的a |
正向负向断言 | (?!exp) | 匹配不满足 exp 前面的位置 | a(?!b) 匹配ac中的a,不匹配ab中的a |
反向先行断言 | (?<=exp) | 匹配 exp 后面的位置 | (? 匹配ab中的b,不匹配cb中的b` |
反向负向断言 | (?<!exp) | 匹配不满足 exp 后面的位置 | (?b 匹配cb中的b,不匹配ab中的b |
示例:匹配密码中包含至少 1 个大写字母、1 个小写字母和 1 个数字
- 正则表达式:^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,16}$
- 解析:
- (?=.*[A-Z]):确保字符串中存在大写字母;
- (?=.*[a-z]):确保存在小写字母;
- (?=.*\d):确保存在数字;
- .{8,16}:匹配 8-16 位任意字符。
五、避坑指南
- 转义字符问题:Java 中\需要双重转义(如\d需写为\\d),Python 中直接写\d即可;
- 性能问题:复杂正则表达式(如嵌套量词.*.*)可能导致回溯爆炸,尽量简化表达式(如用\d+代替\d*\d);
- 边界匹配:验证全字符串时务必加^和$(如手机号验证,否则138123456789会被误判为合法);
- 范围匹配:[0-9a-zA-Z]不能写成[a-z0-9A-Z](顺序不影响),但[a-Z]是错误的(大写字母 ASCII 码小于小写字母)。
六、常用工具推荐
- 在线正则表达式测试工具:Regex101(支持语法高亮、匹配结果实时预览,可选择语言);
- 正则表达式生成器:Regex Generator(输入示例文本,自动生成正则表达式);
- IDE 集成工具:IntelliJ IDEA、VS Code 均内置正则表达式测试功能(快捷键 Ctrl+F,开启 Regex 模式)。