|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
正则表达式(Regular Expression,简称regex)是一种强大而灵活的文本处理工具,它使用特定的模式来匹配、查找、替换和验证文本。无论是数据清洗、日志分析、信息提取还是表单验证,正则表达式都能帮助我们以简洁高效的方式完成复杂的文本处理任务。掌握正则表达式不仅能显著提升工作效率,还能让我们在面对各种文本处理挑战时游刃有余。
本文将从基础概念讲起,逐步深入到高级技巧,通过大量实例和代码演示,帮助读者全面掌握正则表达式的使用方法,让文本处理变得简单高效。
正则表达式基础
什么是正则表达式
正则表达式是由普通字符(如字母、数字)和特殊字符(称为”元字符”)组成的文字模式,它描述了一种字符串匹配的模式。正则表达式可以用来检查一个字符串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
基本语法
在开始学习正则表达式之前,我们需要了解一些基本语法:
1. 字面量字符:普通字符如字母、数字等,它们会匹配自身。例如,正则表达式cat会匹配字符串中的”cat”。
2. 元字符:具有特殊含义的字符,如.、*、+、?、|、(、)、[、]、{、}、^、$、\等。
3. 转义字符:使用反斜杠\来改变字符的原有含义,例如\.匹配点字符本身,而不是任意字符。
字面量字符:普通字符如字母、数字等,它们会匹配自身。例如,正则表达式cat会匹配字符串中的”cat”。
元字符:具有特殊含义的字符,如.、*、+、?、|、(、)、[、]、{、}、^、$、\等。
转义字符:使用反斜杠\来改变字符的原有含义,例如\.匹配点字符本身,而不是任意字符。
正则表达式的创建方式
在大多数编程语言中,有两种创建正则表达式的方式:
1. 字面量方式(JavaScript示例):
- const pattern = /pattern/flags;
复制代码
1. 构造函数方式(JavaScript示例):
- const pattern = new RegExp('pattern', 'flags');
复制代码
其中,flags是可选的修饰符,常见的有:
• g:全局匹配
• i:不区分大小写
• m:多行模式
常用元字符详解
字符类
字符类用于匹配一组字符中的任意一个。
1. 简单字符类:使用方括号[]定义,例如[abc]匹配”a”、”b”或”c”中的任意一个。
- const pattern = /[abc]/;
- console.log(pattern.test('a')); // true
- console.log(pattern.test('b')); // true
- console.log(pattern.test('c')); // true
- console.log(pattern.test('d')); // false
复制代码
1. 范围字符类:使用连字符-表示范围,例如[a-z]匹配任意小写字母,[0-9]匹配任意数字。
- const pattern = /[a-z]/;
- console.log(pattern.test('a')); // true
- console.log(pattern.test('Z')); // false
- console.log(pattern.test('5')); // false
复制代码
1. 否定字符类:使用^表示否定,例如[^abc]匹配除”a”、”b”、”c”之外的任意字符。
- const pattern = /[^abc]/;
- console.log(pattern.test('a')); // false
- console.log(pattern.test('d')); // true
- console.log(pattern.test('5')); // true
复制代码
1. 预定义字符类:\d:匹配任意数字,等同于[0-9]\D:匹配任意非数字,等同于[^0-9]\w:匹配任意单词字符(字母、数字、下划线),等同于[a-zA-Z0-9_]\W:匹配任意非单词字符,等同于[^a-zA-Z0-9_]\s:匹配任意空白字符(空格、制表符、换行符等)\S:匹配任意非空白字符
2. \d:匹配任意数字,等同于[0-9]
3. \D:匹配任意非数字,等同于[^0-9]
4. \w:匹配任意单词字符(字母、数字、下划线),等同于[a-zA-Z0-9_]
5. \W:匹配任意非单词字符,等同于[^a-zA-Z0-9_]
6. \s:匹配任意空白字符(空格、制表符、换行符等)
7. \S:匹配任意非空白字符
• \d:匹配任意数字,等同于[0-9]
• \D:匹配任意非数字,等同于[^0-9]
• \w:匹配任意单词字符(字母、数字、下划线),等同于[a-zA-Z0-9_]
• \W:匹配任意非单词字符,等同于[^a-zA-Z0-9_]
• \s:匹配任意空白字符(空格、制表符、换行符等)
• \S:匹配任意非空白字符
- const digitPattern = /\d/;
- const wordPattern = /\w/;
- const spacePattern = /\s/;
- console.log(digitPattern.test('5')); // true
- console.log(digitPattern.test('a')); // false
- console.log(wordPattern.test('a')); // true
- console.log(wordPattern.test('5')); // true
- console.log(wordPattern.test('_')); // true
- console.log(wordPattern.test('@')); // false
- console.log(spacePattern.test(' ')); // true
- console.log(spacePattern.test('\t')); // true
- console.log(spacePattern.test('a')); // false
复制代码
量词
量词用于指定前面的字符或字符类出现的次数。
1. *:匹配前面的元素零次或多次。
- const pattern = /ab*c/;
- console.log(pattern.test('ac')); // true
- console.log(pattern.test('abc')); // true
- console.log(pattern.test('abbc')); // true
- console.log(pattern.test('aXc')); // false
复制代码
1. +:匹配前面的元素一次或多次。
- const pattern = /ab+c/;
- console.log(pattern.test('ac')); // false
- console.log(pattern.test('abc')); // true
- console.log(pattern.test('abbc')); // true
- console.log(pattern.test('aXc')); // false
复制代码
1. ?:匹配前面的元素零次或一次。
- const pattern = /ab?c/;
- console.log(pattern.test('ac')); // true
- console.log(pattern.test('abc')); // true
- console.log(pattern.test('abbc')); // false
- console.log(pattern.test('aXc')); // false
复制代码
1. {n}:匹配前面的元素恰好n次。
- const pattern = /ab{2}c/;
- console.log(pattern.test('abc')); // false
- console.log(pattern.test('abbc')); // true
- console.log(pattern.test('abbbc')); // false
复制代码
1. {n,}:匹配前面的元素至少n次。
- const pattern = /ab{2,}c/;
- console.log(pattern.test('abc')); // false
- console.log(pattern.test('abbc')); // true
- console.log(pattern.test('abbbc')); // true
复制代码
1. {n,m}:匹配前面的元素至少n次,至多m次。
- const pattern = /ab{2,3}c/;
- console.log(pattern.test('abc')); // false
- console.log(pattern.test('abbc')); // true
- console.log(pattern.test('abbbc')); // true
- console.log(pattern.test('abbbbc')); // false
复制代码
边界匹配
边界匹配用于指定匹配位置,而不是字符。
1. ^:匹配字符串的开始。
- const pattern = /^abc/;
- console.log(pattern.test('abc def')); // true
- console.log(pattern.test('def abc')); // false
复制代码
1. $:匹配字符串的结束。
- const pattern = /abc$/;
- console.log(pattern.test('def abc')); // true
- console.log(pattern.test('abc def')); // false
复制代码
1. \b:匹配单词边界。
- const pattern = /\bcat\b/;
- console.log(pattern.test('The cat is cute')); // true
- console.log(pattern.test('The caterpillar is big')); // false
复制代码
1. \B:匹配非单词边界。
- const pattern = /\Bcat\B/;
- console.log(pattern.test('The caterpillar is big')); // true
- console.log(pattern.test('The cat is cute')); // false
复制代码
分组和引用
1. 捕获组:使用圆括号()创建捕获组,可以将多个元素组合为一个单元,并记住匹配的文本。
- const pattern = /(\d{3})-(\d{2})-(\d{4})/;
- const match = pattern.exec('123-45-6789');
- console.log(match[0]); // '123-45-6789' (完整匹配)
- console.log(match[1]); // '123' (第一个捕获组)
- console.log(match[2]); // '45' (第二个捕获组)
- console.log(match[3]); // '6789' (第三个捕获组)
复制代码
1. 非捕获组:使用(?:)创建非捕获组,它组合元素但不创建捕获组。
- const pattern = /(?:\d{3})-(\d{2})-(\d{4})/;
- const match = pattern.exec('123-45-6789');
- console.log(match[0]); // '123-45-6789' (完整匹配)
- console.log(match[1]); // '45' (第一个捕获组)
- console.log(match[2]); // '6789' (第二个捕获组)
复制代码
1. 反向引用:使用\n(n是数字)引用前面的捕获组。
- const pattern = /(\w+)\s+\1/;
- console.log(pattern.test('hello hello')); // true
- console.log(pattern.test('hello world')); // false
复制代码
选择符
使用竖线|表示选择,匹配多个可能的模式之一。
- const pattern = /cat|dog|bird/;
- console.log(pattern.test('I have a cat')); // true
- console.log(pattern.test('I have a dog')); // true
- console.log(pattern.test('I have a bird')); // true
- console.log(pattern.test('I have a fish')); // false
复制代码
实用技巧与模式
电子邮件验证
- const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
- console.log(emailPattern.test('user@example.com')); // true
- console.log(emailPattern.test('user.name@example.co.uk')); // true
- console.log(emailPattern.test('user@example')); // false
- console.log(emailPattern.test('user.example.com')); // false
复制代码
URL验证
- const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
- console.log(urlPattern.test('https://www.example.com')); // true
- console.log(urlPattern.test('http://subdomain.example.com/path/page.html')); // true
- console.log(urlPattern.test('www.example.com')); // true
- console.log(urlPattern.test('example')); // false
复制代码
电话号码验证
- // 美国电话号码格式
- const phonePattern = /^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
- console.log(phonePattern.test('(123) 456-7890')); // true
- console.log(phonePattern.test('123-456-7890')); // true
- console.log(phonePattern.test('123.456.7890')); // true
- console.log(phonePattern.test('1234567890')); // true
- console.log(phonePattern.test('123-456')); // false
复制代码
日期格式验证
- // YYYY-MM-DD 格式
- const datePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
- console.log(datePattern.test('2023-05-15')); // true
- console.log(datePattern.test('2023-13-15')); // false (无效月份)
- console.log(datePattern.test('2023-02-30')); // false (无效日期,虽然格式正确)
复制代码
HTML标签提取
- const html = '<div><p>Paragraph 1</p><p>Paragraph 2</p></div>';
- const tagPattern = /<(\w+)>/g;
- let match;
- while ((match = tagPattern.exec(html)) !== null) {
- console.log(match[1]); // 输出: 'div', 'p', 'p'
- }
复制代码
密码强度验证
- // 至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符
- const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
- console.log(passwordPattern.test('Password123!')); // true
- console.log(passwordPattern.test('password')); // false
- console.log(passwordPattern.test('PASSWORD123!')); // false
- console.log(passwordPattern.test('Password123')); // false
复制代码
移除多余空格
- function removeExtraSpaces(str) {
- return str.replace(/\s+/g, ' ').trim();
- }
- console.log(removeExtraSpaces('Hello world! How are you?')); // 'Hello world! How are you?'
复制代码
驼峰命名转换
- // 将连字符或下划线分隔的字符串转换为驼峰命名
- function toCamelCase(str) {
- return str.replace(/[-_\s]+(.)?/g, (match, char) => char ? char.toUpperCase() : '');
- }
- console.log(toCamelCase('background-color')); // 'backgroundColor'
- console.log(toCamelCase('font_size')); // 'fontSize'
- console.log(toCamelCase('border width')); // 'borderWidth'
复制代码
提取文本中的数字
- function extractNumbers(str) {
- return str.match(/\d+\.?\d*/g) || [];
- }
- console.log(extractNumbers('The price is $19.99 and the discount is 5.5%')); // ['19.99', '5.5']
- console.log(extractNumbers('There are no numbers here')); // []
复制代码
在不同编程语言中的应用
JavaScript
在JavaScript中,正则表达式可以通过RegExp对象或字面量方式创建。
- // 创建正则表达式
- const pattern1 = new RegExp('pattern', 'gi');
- const pattern2 = /pattern/gi;
- // 常用方法
- const str = 'The quick brown fox jumps over the lazy dog.';
- // test() - 测试字符串是否匹配模式
- console.log(/fox/.test(str)); // true
- // exec() - 执行匹配,返回结果数组
- const result = /fox/.exec(str);
- console.log(result[0]); // 'fox'
- console.log(result.index); // 16
- // match() - 返回匹配结果数组
- console.log(str.match(/the/gi)); // ['The', 'the']
- // search() - 返回匹配位置的索引
- console.log(str.search(/fox/)); // 16
- // replace() - 替换匹配的文本
- console.log(str.replace(/fox/, 'cat')); // 'The quick brown cat jumps over the lazy dog.'
- // split() - 使用正则表达式分割字符串
- console.log(str.split(/\s+/)); // ['The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog.']
复制代码
Python
在Python中,使用re模块处理正则表达式。
- import re
- # 编译正则表达式
- pattern = re.compile(r'pattern')
- # 常用方法
- text = 'The quick brown fox jumps over the lazy dog.'
- # match() - 从字符串开始处匹配
- result = re.match(r'The', text)
- print(result.group() if result else 'No match') # 'The'
- # search() - 在整个字符串中搜索
- result = re.search(r'fox', text)
- print(result.group() if result else 'No match') # 'fox'
- # findall() - 查找所有匹配
- print(re.findall(r'the', text, re.IGNORECASE)) # ['The', 'the']
- # finditer() - 返回迭代器
- for match in re.finditer(r'\b\w{4}\b', text):
- print(f'Found {match.group()} at position {match.start()}')
- # sub() - 替换匹配的文本
- print(re.sub(r'fox', 'cat', text)) # 'The quick brown cat jumps over the lazy dog.'
- # split() - 使用正则表达式分割字符串
- print(re.split(r'\s+', text)) # ['The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog.']
复制代码
Java
在Java中,使用java.util.regex包处理正则表达式。
- import java.util.regex.*;
- public class RegexExample {
- public static void main(String[] args) {
- // 编译正则表达式
- Pattern pattern = Pattern.compile("pattern");
-
- String text = "The quick brown fox jumps over the lazy dog.";
-
- // matches() - 测试整个字符串是否匹配
- System.out.println(text.matches(".*fox.*")); // true
-
- // 查找匹配
- Matcher matcher = pattern.matcher(text);
-
- // find() - 查找下一个匹配
- if (matcher.find()) {
- System.out.println("Found match: " + matcher.group());
- }
-
- // 使用Pattern和Matcher进行复杂匹配
- Pattern wordPattern = Pattern.compile("\\b\\w{4}\\b");
- Matcher wordMatcher = wordPattern.matcher(text);
-
- while (wordMatcher.find()) {
- System.out.println("Found word: " + wordMatcher.group() +
- " at position " + wordMatcher.start());
- }
-
- // replaceAll() - 替换所有匹配
- String replaced = text.replaceAll("fox", "cat");
- System.out.println(replaced); // 'The quick brown cat jumps over the lazy dog.'
-
- // split() - 使用正则表达式分割字符串
- String[] words = text.split("\\s+");
- for (String word : words) {
- System.out.println(word);
- }
- }
- }
复制代码
PHP
在PHP中,使用PCRE(Perl Compatible Regular Expressions)函数处理正则表达式。
- <?php
- $text = 'The quick brown fox jumps over the lazy dog.';
- // preg_match() - 执行匹配
- if (preg_match('/fox/', $text, $matches)) {
- echo "Found match: " . $matches[0] . "\n"; // 'fox'
- }
- // preg_match_all() - 查找所有匹配
- if (preg_match_all('/\b\w{4}\b/', $text, $matches)) {
- print_r($matches[0]); // Array: [0] => 'quick', [1] => 'brown', [2] => 'over', [3] => 'lazy'
- }
- // preg_replace() - 替换匹配的文本
- $replaced = preg_replace('/fox/', 'cat', $text);
- echo $replaced . "\n"; // 'The quick brown cat jumps over the lazy dog.'
- // preg_split() - 使用正则表达式分割字符串
- $words = preg_split('/\s+/', $text);
- print_r($words);
- // preg_grep() - 返回匹配模式的数组元素
- $array = ['apple', 'banana', 'cherry', 'date'];
- $result = preg_grep('/^a/', $array);
- print_r($result); // Array: [0] => 'apple'
- ?>
复制代码
Ruby
在Ruby中,正则表达式是语言的一部分,使用//或%r{}创建。
- text = 'The quick brown fox jumps over the lazy dog.'
- # =~ - 匹配操作符
- puts text =~ /fox/ # 16 (匹配位置的索引)
- # match() - 执行匹配
- match_data = text.match(/fox/)
- puts match_data[0] if match_data # 'fox'
- # scan() - 查找所有匹配
- words = text.scan(/\b\w{4}\b/)
- puts words.inspect # ["quick", "brown", "over", "lazy"]
- # gsub() - 全局替换
- replaced = text.gsub(/fox/, 'cat')
- puts replaced # 'The quick brown cat jumps over the lazy dog.'
- # split() - 使用正则表达式分割字符串
- words = text.split(/\s+/)
- puts words.inspect # ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog."]
复制代码
实际案例分析
案例1:日志文件分析
假设我们有一个Web服务器日志文件,需要从中提取特定信息。
- // 示例日志行
- const logLine = '192.168.1.1 - - [10/Oct/2023:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326 "http://www.example.com/start.html" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"';
- // 提取IP地址
- const ipPattern = /^(\d+\.\d+\.\d+\.\d+)/;
- const ipMatch = logLine.match(ipPattern);
- console.log('IP Address:', ipMatch ? ipMatch[1] : 'Not found');
- // 提取时间戳
- const timestampPattern = /\[(.*?)\]/;
- const timestampMatch = logLine.match(timestampPattern);
- console.log('Timestamp:', timestampMatch ? timestampMatch[1] : 'Not found');
- // 提取请求方法、URL和协议
- const requestPattern = /"(\w+) (\S+) (\S+)"/;
- const requestMatch = logLine.match(requestPattern);
- if (requestMatch) {
- console.log('Method:', requestMatch[1]);
- console.log('URL:', requestMatch[2]);
- console.log('Protocol:', requestMatch[3]);
- }
- // 提取状态码
- const statusPattern = /" (\d{3}) /;
- const statusMatch = logLine.match(statusPattern);
- console.log('Status Code:', statusMatch ? statusMatch[1] : 'Not found');
- // 提取响应大小
- const sizePattern = /" \d{3} (\d+)/;
- const sizeMatch = logLine.match(sizePattern);
- console.log('Response Size:', sizeMatch ? sizeMatch[1] : 'Not found');
- // 提取引用页面
- const referrerPattern = /" (\S+) "/;
- const referrerMatches = logLine.match(referrerPattern);
- console.log('Referrer:', referrerMatches && referrerMatches.length > 1 ? referrerMatches[1] : 'Not found');
- // 提取用户代理
- const userAgentPattern = /"([^"]+)"$/;
- const userAgentMatch = logLine.match(userAgentPattern);
- console.log('User Agent:', userAgentMatch ? userAgentMatch[1] : 'Not found');
复制代码
案例2:数据清洗
假设我们有一个包含用户信息的CSV文件,但数据格式不一致,需要清洗。
- // 原始数据
- const rawData = [
- 'John,Doe,john@example.com, (555) 123-4567',
- 'Jane Smith, jane.smith@test.com, 555.987.6543',
- ' Bob Johnson ,bob@domain.com,,',
- 'Alice Williams, alice@test.org, 555-234-5678'
- ];
- // 清洗函数
- function cleanUserData(rawData) {
- const cleanedData = [];
-
- for (const line of rawData) {
- // 提取姓名(可能包含逗号)
- const namePattern = /^([^,]+?),\s*([^,]+?)\s*,/;
- const nameMatch = line.match(namePattern);
-
- let firstName = '';
- let lastName = '';
-
- if (nameMatch) {
- firstName = nameMatch[1].trim();
- lastName = nameMatch[2].trim();
- } else {
- // 如果没有逗号分隔的姓名,尝试其他格式
- const simpleNamePattern = /^([^,]+?)\s*,/;
- const simpleNameMatch = line.match(simpleNamePattern);
- if (simpleNameMatch) {
- const fullName = simpleNameMatch[1].trim();
- const nameParts = fullName.split(/\s+/);
- firstName = nameParts[0] || '';
- lastName = nameParts.slice(1).join(' ') || '';
- }
- }
-
- // 提取电子邮件
- const emailPattern = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/;
- const emailMatch = line.match(emailPattern);
- const email = emailMatch ? emailMatch[1] : '';
-
- // 提取电话号码
- const phonePattern = /(\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})/;
- const phoneMatch = line.match(phonePattern);
- let phone = phoneMatch ? phoneMatch[1] : '';
-
- // 标准化电话号码格式
- if (phone) {
- phone = phone.replace(/[^\d]/g, '');
- if (phone.length === 10) {
- phone = `(${phone.substring(0, 3)}) ${phone.substring(3, 6)}-${phone.substring(6)}`;
- }
- }
-
- cleanedData.push({
- firstName,
- lastName,
- email,
- phone
- });
- }
-
- return cleanedData;
- }
- const cleanedData = cleanUserData(rawData);
- console.log(cleanedData);
- /*
- 输出:
- [
- {
- firstName: 'John',
- lastName: 'Doe',
- email: 'john@example.com',
- phone: '(555) 123-4567'
- },
- {
- firstName: 'Jane',
- lastName: 'Smith',
- email: 'jane.smith@test.com',
- phone: '(555) 987-6543'
- },
- {
- firstName: 'Bob',
- lastName: 'Johnson',
- email: 'bob@domain.com',
- phone: ''
- },
- {
- firstName: 'Alice',
- lastName: 'Williams',
- email: 'alice@test.org',
- phone: '(555) 234-5678'
- }
- ]
- */
复制代码
案例3:HTML解析与提取
虽然正则表达式不是解析HTML的最佳工具(HTML解析器更合适),但在某些简单场景下,我们可以使用正则表达式提取特定信息。
- // 示例HTML
- const html = `
- <html>
- <head>
- <title>Sample Page</title>
- <meta name="description" content="This is a sample page">
- <meta name="keywords" content="sample, page, example">
- </head>
- <body>
- <h1>Welcome to the Sample Page</h1>
- <div class="content">
- <p>This is the first paragraph.</p>
- <p>This is the second paragraph with a <a href="https://example.com">link</a>.</p>
- <img src="image.jpg" alt="Sample image">
- </div>
- <div class="footer">
- <p>Contact us at <a href="mailto:info@example.com">info@example.com</a></p>
- </div>
- </body>
- </html>
- `;
- // 提取页面标题
- const titlePattern = /<title>(.*?)<\/title>/;
- const titleMatch = html.match(titlePattern);
- console.log('Page Title:', titleMatch ? titleMatch[1] : 'Not found');
- // 提取所有链接
- const linkPattern = /<a\s+(?:[^>]*?\s+)?href="([^"]*)"/g;
- const links = [];
- let linkMatch;
- while ((linkMatch = linkPattern.exec(html)) !== null) {
- links.push(linkMatch[1]);
- }
- console.log('Links:', links);
- // 提取所有图片
- const imagePattern = /<img\s+(?:[^>]*?\s+)?src="([^"]*)"/g;
- const images = [];
- let imageMatch;
- while ((imageMatch = imagePattern.exec(html)) !== null) {
- images.push(imageMatch[1]);
- }
- console.log('Images:', images);
- // 提取meta标签内容
- const metaPattern = /<meta\s+name="([^"]*)"\s+content="([^"]*)"/g;
- const metaTags = {};
- let metaMatch;
- while ((metaMatch = metaPattern.exec(html)) !== null) {
- metaTags[metaMatch[1]] = metaMatch[2];
- }
- console.log('Meta Tags:', metaTags);
- // 提取特定div的内容
- const contentPattern = /<div class="content">(.*?)<\/div>/s;
- const contentMatch = html.match(contentPattern);
- console.log('Content Div:', contentMatch ? contentMatch[1].trim() : 'Not found');
- // 提取所有段落文本
- const paragraphPattern = /<p>(.*?)<\/p>/g;
- const paragraphs = [];
- let paragraphMatch;
- while ((paragraphMatch = paragraphPattern.exec(html)) !== null) {
- paragraphs.push(paragraphMatch[1]);
- }
- console.log('Paragraphs:', paragraphs);
复制代码
案例4:代码重构
假设我们有一段JavaScript代码,需要将其中的函数调用从回调风格转换为Promise风格。
- // 原始代码(回调风格)
- const originalCode = `
- function fetchData(callback) {
- setTimeout(() => {
- callback(null, { data: 'Some data' });
- }, 1000);
- }
- function processData(data, callback) {
- setTimeout(() => {
- callback(null, { processedData: data.data.toUpperCase() });
- }, 500);
- }
- function saveData(processedData, callback) {
- setTimeout(() => {
- callback(null, { success: true });
- }, 800);
- }
- fetchData((err, data) => {
- if (err) throw err;
- processData(data, (err, processedData) => {
- if (err) throw err;
- saveData(processedData, (err, result) => {
- if (err) throw err;
- console.log('Data saved successfully');
- });
- });
- });
- `;
- // 转换为Promise风格
- function convertToPromises(code) {
- // 1. 转换函数定义
- let convertedCode = code.replace(
- /function\s+(\w+)\s*\(([^)]*),\s*callback\s*\)\s*{([^}]+)callback\s*\(\s*null,\s*([^)]+)\s*\)\s*;?\s*}/g,
- (match, functionName, params, body, result) => {
- return `function ${functionName}(${params}) {
- return new Promise((resolve, reject) => {
- ${body}resolve(${result});
- });
- }`;
- }
- );
-
- // 2. 转换函数调用
- convertedCode = convertedCode.replace(
- /(\w+)\(([^)]+)\)\s*{\s*if\s+\(err\)\s+throw\s+err\s*;\s*([^}]+)\s*}/g,
- (match, functionName, params, nextCall) => {
- return `${functionName}(${params})
- .then(${nextCall})`;
- }
- );
-
- // 3. 转换最外层调用
- convertedCode = convertedCode.replace(
- /(\w+)\(([^)]+)\)\s*=>\s*{\s*if\s+\(err\)\s+throw\s+err\s*;\s*([^}]+)\s*}/g,
- (match, functionName, params, nextCall) => {
- return `${functionName}(${params})
- .then(${nextCall})`;
- }
- );
-
- // 4. 转换最后的回调
- convertedCode = convertedCode.replace(
- /\.then\(\s*([^)]+)\s*=>\s*{\s*if\s+\(err\)\s+throw\s+err\s*;\s*console\.log\(([^)]+)\)\s*;\s*}\s*\)/,
- '.then($1 => {\n console.log($2);\n })'
- );
-
- return convertedCode;
- }
- const convertedCode = convertToPromises(originalCode);
- console.log(convertedCode);
- /*
- 输出:
- function fetchData() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve({ data: 'Some data' });
- }, 1000);
- });
- }
- function processData(data) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve({ processedData: data.data.toUpperCase() });
- }, 500);
- });
- }
- function saveData(processedData) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve({ success: true });
- }, 800);
- });
- }
- fetchData()
- .then(data => processData(data))
- .then(processedData => saveData(processedData))
- .then(result => {
- console.log('Data saved successfully');
- });
- */
复制代码
高级技巧和性能优化
贪婪与懒惰匹配
默认情况下,量词是”贪婪”的,会匹配尽可能多的字符。有时我们需要”懒惰”匹配,匹配尽可能少的字符。
- const text = '<div>Content 1</div><div>Content 2</div>';
- // 贪婪匹配
- const greedyPattern = /<div>.*<\/div>/;
- console.log(text.match(greedyPattern)[0]); // '<div>Content 1</div><div>Content 2</div>'
- // 懒惰匹配(使用?)
- const lazyPattern = /<div>.*?<\/div>/;
- console.log(text.match(lazyPattern)[0]); // '<div>Content 1</div>'
复制代码
前瞻和后顾
前瞻和后顾用于匹配某些条件满足或不满足的位置,而不消耗字符。
1. 正向前瞻:(?=pattern)- 匹配后面跟着pattern的位置
- const text = 'I have 100 dollars and 200 euros.';
- const pattern = /\d+(?=\s+dollars)/;
- console.log(text.match(pattern)[0]); // '100'
复制代码
1. 负向前瞻:(?!pattern)- 匹配后面不跟着pattern的位置
- const text = 'I have 100 dollars and 200 euros.';
- const pattern = /\d+(?!\s+dollars)/;
- console.log(text.match(pattern)[0]); // '200'
复制代码
1. 正向后顾:(?<=pattern)- 匹配前面是pattern的位置(ES2018支持)
- const text = 'Price: $100, Total: $200';
- const pattern = /(?<=\$)\d+/;
- console.log(text.match(pattern)[0]); // '100'
复制代码
1. 负向后顾:(?<!pattern)- 匹配前面不是pattern的位置(ES2018支持)
- const text = '100 dollars, 200 euros';
- const pattern = /(?<!\$)\d+/;
- console.log(text.match(pattern)[0]); // '100'
复制代码
原子组
原子组(?>...)一旦匹配就不会回溯,可以提高性能并防止某些回溯问题。
- // 普通组(会回溯)
- const text = 'abcde';
- const pattern1 = /(a\w+)(a\w+)/;
- console.log(pattern1.exec(text)); // null,因为第一个组会匹配整个字符串,然后回溯
- // 原子组(不会回溯)
- const pattern2 = /(?>a\w+)(a\w+)/;
- console.log(pattern2.exec(text)); // null,更快失败
复制代码
回溯控制
回溯是正则表达式引擎尝试匹配的过程,但过多的回溯会导致性能问题。
- // 可能导致灾难性回溯的模式
- const text = 'aaaaaaaaaaaaaaaaaaaaaaaaX';
- const badPattern = /^(a+)+$/;
- console.log(badPattern.test(text)); // 非常慢
- // 优化后的模式
- const goodPattern = /^a+$/;
- console.log(goodPattern.test(text)); // 快速
复制代码
性能优化技巧
1. 避免嵌套量词:如(a+)*可能导致灾难性回溯。
2. 使用具体字符类:如\d比[0-9]更快,.比\d\D更快。
3. 使用非捕获组:(?:...)比捕获组(...)更快,因为不需要保存匹配。
4. 避免过度回溯:使用原子组或占有量词*+,++,?+,{n,m}+来防止回溯。
5. 锚定模式:使用^和$锚定模式,避免不必要的搜索。
6. 预编译正则表达式:在循环中重复使用时,预编译正则表达式可以提高性能。
避免嵌套量词:如(a+)*可能导致灾难性回溯。
使用具体字符类:如\d比[0-9]更快,.比\d\D更快。
使用非捕获组:(?:...)比捕获组(...)更快,因为不需要保存匹配。
避免过度回溯:使用原子组或占有量词*+,++,?+,{n,m}+来防止回溯。
锚定模式:使用^和$锚定模式,避免不必要的搜索。
预编译正则表达式:在循环中重复使用时,预编译正则表达式可以提高性能。
- // 不好的做法(每次循环都重新编译)
- const strings = ['test1', 'test2', 'test3'];
- for (const str of strings) {
- console.log(/test\d/.test(str));
- }
- // 好的做法(预编译正则表达式)
- const pattern = /test\d/;
- for (const str of strings) {
- console.log(pattern.test(str));
- }
复制代码
调试正则表达式
调试复杂的正则表达式可能很困难,以下是一些技巧:
1. 使用在线工具:如Regex101、Debuggex等,可以可视化正则表达式的匹配过程。
2. 分解复杂模式:将复杂的正则表达式分解为多个简单部分,分别测试。
3. 添加注释:使用x标志(在某些语言中)可以添加注释和空白,使正则表达式更易读。
使用在线工具:如Regex101、Debuggex等,可以可视化正则表达式的匹配过程。
分解复杂模式:将复杂的正则表达式分解为多个简单部分,分别测试。
添加注释:使用x标志(在某些语言中)可以添加注释和空白,使正则表达式更易读。
- // 使用x标志(JavaScript不支持,但其他语言如PHP、Python支持)
- // 这里仅作示例
- const pattern = /
- \b # 单词边界
- (\w+) # 捕获单词
- \s # 空白字符
- (\w+) # 捕获另一个单词
- \b # 单词边界
- /x;
复制代码
1. 使用命名捕获组:给捕获组命名,使代码更易读和维护。
- const text = 'John Doe, age: 30';
- const pattern = /(?<name>\w+\s+\w+),\s+age:\s+(?<age>\d+)/;
- const match = text.match(pattern);
- if (match) {
- console.log('Name:', match.groups.name); // 'John Doe'
- console.log('Age:', match.groups.age); // '30'
- }
复制代码
总结与进阶学习资源
正则表达式是一种强大而灵活的文本处理工具,掌握它可以显著提高文本处理的效率和准确性。从基本的字符匹配到复杂的模式提取,正则表达式在各种编程语言和文本处理场景中都有广泛应用。
关键要点回顾
1. 基础语法:掌握字面量字符、元字符、字符类、量词和边界匹配等基本概念。
2. 高级特性:理解捕获组、非捕获组、反向引用、前瞻和后顾等高级特性。
3. 实用模式:熟悉常见的正则表达式模式,如电子邮件验证、URL验证、电话号码验证等。
4. 性能优化:了解如何编写高效的正则表达式,避免灾难性回溯和性能问题。
5. 多语言应用:掌握正则表达式在不同编程语言中的使用方法和差异。
基础语法:掌握字面量字符、元字符、字符类、量词和边界匹配等基本概念。
高级特性:理解捕获组、非捕获组、反向引用、前瞻和后顾等高级特性。
实用模式:熟悉常见的正则表达式模式,如电子邮件验证、URL验证、电话号码验证等。
性能优化:了解如何编写高效的正则表达式,避免灾难性回溯和性能问题。
多语言应用:掌握正则表达式在不同编程语言中的使用方法和差异。
进阶学习资源
1. 书籍:《精通正则表达式》(Mastering Regular Expressions)- Jeffrey E.F. Friedl《正则表达式必知必会》(Regular Expressions Cookbook)- Jan Goyvaerts, Steven Levithan
2. 《精通正则表达式》(Mastering Regular Expressions)- Jeffrey E.F. Friedl
3. 《正则表达式必知必会》(Regular Expressions Cookbook)- Jan Goyvaerts, Steven Levithan
4. 在线工具:Regex101 (https://regex101.com/) - 正则表达式测试和调试工具Debuggex (https://www.debuggex.com/) - 可视化正则表达式调试工具RegExr (https://regexr.com/) - 学习、构建和测试正则表达式
5. Regex101 (https://regex101.com/) - 正则表达式测试和调试工具
6. Debuggex (https://www.debuggex.com/) - 可视化正则表达式调试工具
7. RegExr (https://regexr.com/) - 学习、构建和测试正则表达式
8. 教程和文档:MDN Web Docs - JavaScript正则表达式指南Python官方文档 - re模块文档Java官方文档 - Pattern类文档
9. MDN Web Docs - JavaScript正则表达式指南
10. Python官方文档 - re模块文档
11. Java官方文档 - Pattern类文档
12. 练习网站:RegexOne (https://regexone.com/) - 交互式正则表达式教程Regex Crossword (https://regexcrossword.com/) - 通过解谜学习正则表达式
13. RegexOne (https://regexone.com/) - 交互式正则表达式教程
14. Regex Crossword (https://regexcrossword.com/) - 通过解谜学习正则表达式
书籍:
• 《精通正则表达式》(Mastering Regular Expressions)- Jeffrey E.F. Friedl
• 《正则表达式必知必会》(Regular Expressions Cookbook)- Jan Goyvaerts, Steven Levithan
在线工具:
• Regex101 (https://regex101.com/) - 正则表达式测试和调试工具
• Debuggex (https://www.debuggex.com/) - 可视化正则表达式调试工具
• RegExr (https://regexr.com/) - 学习、构建和测试正则表达式
教程和文档:
• MDN Web Docs - JavaScript正则表达式指南
• Python官方文档 - re模块文档
• Java官方文档 - Pattern类文档
练习网站:
• RegexOne (https://regexone.com/) - 交互式正则表达式教程
• Regex Crossword (https://regexcrossword.com/) - 通过解谜学习正则表达式
通过不断练习和应用,你将能够熟练掌握正则表达式,轻松应对各种复杂的文本处理任务,显著提升工作效率。记住,正则表达式是一门需要实践才能掌握的技能,多写多用才能真正精通。 |
|