正则表达式是一个用于匹配、提取、替换的包含元字符的字符串。
正则表达式中用\进行转义。
基本元字符
. 任意的一个非换行字符
[] 集合匹配,表示匹配一个出现在[]中的一个字符
| 或:a|ab,敏感词:病毒|大麻
() 提高优先级实现分组,abc->a(bc)
限定元字符
+ 紧跟在+前面的字符出现>=1次
* 紧跟在*前面的字符出现>=0次
? 紧跟在?前面的字符出现0或1次
{n} [0123456789]{11}表示数字出现11次
{n,} [5-9]{8,}表示5-9之间的数字出现>=8次,{1,}等价于+
{n,m}[0-9]{3,6}表示0-9之间的数字出现3-5次
\w 匹配包括下划线在内的任意单词字符(包括汉字),约等价于[0-9a-zA-Z_]
\W 匹配任何非单词字符,约等价于[^0-9a-zA-Z_]
\s 匹配任何空白字符包括换页符、回车符、换行符、制表符、垂直制表符,
等价于[\f\r\n\t\v]
\S 匹配任何非空白字符,等价于[^\f\r\n\t\v]
\d 匹配一个数字字符,等价于[0-9]
\D 匹配一个非数字字符,等价于[^0-9]
匹配邮箱示例代码:
static void Main(string[] args)
{
WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
string html = wc.DownloadString("http://tieba.baidu.com/p/2314539885");
//【正则表达式1】
//string regex = @"\w+@\w+(\.\w+)+";
//【正则表达式2(改进版)】
string regex = @"[0-9a-zA-Z_\-\.]+@[0-9a-zA-Z_]+(\.[0-9a-zA-Z_]+)+";
MatchCollection mc = Regex.Matches(html, regex);
int i = 0;
foreach (Match match in mc)
{
Console.WriteLine(i++ + "," + match.Value);
}
Console.ReadKey();
}
示例正则表达式解析:
示例邮箱地址:99999@china.com
正则表达式1:@"\w+@\w+(\.\w+)+"
正则表达式1格式解析:
①@符号表示对字符串中的\进行转义
②\w+ 匹配"99999"部分
③@ 匹配"@"
④\w+ 匹配"china"部分
⑤(\.\w+) 匹配".com"部分
⑥(\.\w+)+ 匹配至少一个".com"部分,当邮箱是99999@china.vip.com格式时也能匹配到
⑦表达式1有个问题,即可以匹配到"12345@qq.com楼主好人",因为\w可以匹配汉字,因此有了改进版的表达式2
正则表达式2:@"[0-9a-zA-Z_\-\.]+@[0-9a-zA-Z_]+(.[0-9a-zA-Z_]+)+"
正则表达式2格式解析:
①@符号表示对字符串中的\进行转义
②[0-9a-zA-Z_\-\.]+ 匹配"99999"部分
③@ 匹配"@"
④[0-9a-zA-Z_]+ 匹配"china"部分
⑤(\.[0-9a-zA-Z_]+) 匹配".com"部分
⑥(\.[0-9a-zA-Z_]+)+ 匹配至少一个".com"部分,当邮箱是99999@china.vip.com格式时也能匹配到
⑦表达式2不能匹配到"12345@qq.com楼主好人",能匹配到"12345@qq.com",比表达式1更精确
^和$
once表示只匹配那些以"once"开头的字符串,once[0-9][a-z]end表示只匹配以"once[0-9][a-z]end"开头的字符串。
用两个同样的匹配规则,一个加、一个不加进行匹配结果对比,更容易理解,示例代码如下:
public void TestStart()
{
int i = 0;
bool flag;
string input = "once1aend";
string regex = @"once[0-9][a-z]end";
string regexStart = @"^once[0-9][a-z]end";
Console.WriteLine("【"+input + "匹配" + regexStart + "的结果:】");
flag = Regex.IsMatch(input, regex);
Console.WriteLine("是否匹配=" + flag);
MatchCollection mc = Regex.Matches(input, regexStart);
foreach (Match match in mc)
{
Console.WriteLine(i++ + "," + match.Value);
}
Console.WriteLine("【" + input + "匹配" + regex + "的结果:】");
flag = Regex.IsMatch(input, regex);
Console.WriteLine("是否匹配=" + flag);
Console.WriteLine("匹配项:");
mc = Regex.Matches(input, regex);
i = 0;
foreach (Match match in mc)
{
Console.WriteLine(i++ + "," + match.Value);
}
Console.ReadKey();
}
运行结果如图:
将变量input的值修改为"once1aend666",运行结果如图:
将变量input的值修改为"once1aend666once2bend",运行结果如图:
将变量input的值修改为"666once1aend",运行结果如图:
^对比结论:
- "^once[0-9][a-z]end"只能匹配以此开头的字符串,若字符串中有多条内容符合"once[0-9][a-z]end"格式,也只能匹配到一条,匹配结果是符合匹配格式的字符串开头,建议使Regex.Match()。
- "once[0-9][a-z]end"可以匹配到字符串中满足此格式的任意位置的内容,而且可以匹配到多条,建议使用Regex.Matchs()。
once$表示只匹配那些以"once"结尾的字符串,once[0-9][a-z]end$表示只匹配以"once[0-9][a-z]end"结尾的字符串。
用两个同样的匹配规则,一个加$、一个不加$进行匹配结果对比,更容易理解,示例代码如下:
public void TestEnd()
{
int i = 0;
bool flag;
string input = "once1aend";
string regex = @"once[0-9][a-z]end";
string regexEnd = @"once[0-9][a-z]end$";
Console.WriteLine("【" + input + "匹配" + regexEnd + "的结果:】");
flag = Regex.IsMatch(input, regexEnd);
Console.WriteLine("是否匹配=" + flag);
Console.WriteLine("匹配项:");
//【改进】匹配^...或...$只有一个结果,所以用Match()而不是Matchs()
Match match = Regex.Match(input, regexEnd);
Console.WriteLine(i++ + "," + match.Value);
Console.WriteLine("---------------------------------");
Console.WriteLine("【" + input + "匹配" + regex + "的结果:】");
flag = Regex.IsMatch(input, regex);
Console.WriteLine("是否匹配=" + flag);
Console.WriteLine("匹配项:");
MatchCollection mc = Regex.Matches(input, regex);
i = 0;
foreach (Match obj in mc)
{
Console.WriteLine(i++ + "," + obj.Value);
}
}
运行结果如图:
将变量input的值修改为"once1aend666",运行结果如图:
将变量input的值修改为"once1aend666once2bend",运行结果如图:
将变量input的值修改为"666once1aend",运行结果如图:
$对比结论:
- "once[0-9][a-z]end$"只能匹配以此结尾的字符串,若字符串中有多条内容符合"once[0-9][a-z]end"格式,也只能匹配到一条,匹配结果是符合匹配格式的字符串结尾,建议使Regex.Match()。
- "once[0-9][a-z]end"可以匹配到字符串中满足此格式的任意位置的内容,而且可以匹配到多条,建议使用Regex.Matchs()。
once$表示只匹配"once"字符串,once[0-9][a-z]end$表示只匹配"once[0-9][a-z]end"字符串。
示例代码:
public void TestStratEnd()
{
int i = 0;
bool flag;
string input = "once1aend";
string regex = @"once[0-9][a-z]end";
string regexStartEnd = @"^once[0-9][a-z]end$";
Console.WriteLine("【" + input + "匹配" + regexStartEnd + "的结果:】");
flag = Regex.IsMatch(input, regexStartEnd);
Console.WriteLine("是否匹配=" + flag);
Console.WriteLine("匹配项:");
Match match = Regex.Match(input, regexStartEnd);
Console.WriteLine(i++ + "," + match.Value);
Console.WriteLine("--------------------------------");
Console.WriteLine("【" + input + "匹配" + regex + "的结果:】");
flag = Regex.IsMatch(input, regex);
Console.WriteLine("是否匹配=" + flag);
Console.WriteLine("匹配项:");
MatchCollection mc = Regex.Matches(input, regex);
i = 0;
foreach (Match obj in mc)
{
Console.WriteLine(i++ + "," + obj.Value);
}
}
运行结果如图:
将变量input的值修改为"once1aend666",运行结果如图:
将变量input的值修改为"once1aend666once2bend",运行结果如图:
将变量input的值修改为"666once1aend",运行结果如图:
^$对比结论:
- "^once[0-9][a-z]end$"只能匹配符合"once[0-9][a-z]end"格式且不包含任何其他字符的字符串,建议使Regex.Match()。
- "once[0-9][a-z]end"可以匹配到字符串中满足此格式的任意位置的内容,而且可以匹配到多条,建议使用Regex.Matchs()。
- 当我们需要判断字符串是否是整数、小数类型时,可以使用^$来帮助我们。
匹配HTML标签
使用"<html><body></body></html>"来匹配"<.+>",只能匹配到一个满足项,"html><body></body></html"都被匹配到.+中了,所以只有一个满足项。截图如下:
如果我们相匹配完整的HTML标签时,内容中不应该有">",改进后,使用<html><body></body></html>来匹配"<[^>]+>",能匹配到四个满足项,截图如下(实际中遇到类似情况知道怎么去改进就行了):
使用分组
如果我们在正则提取过程中希望得到匹配项,同时需要使用匹配项中的某一部分的时候,使用分组可以方便的实现这个功能,只需要用圆括号把需要的部分括起来即可,例如:(.+)。
分组编号及使用:
- Match对象有一个Groups的属性,Groups存储正则表达式匹配到的组
- 根据需要加括号对数据进行分组,括号必须成对
- 从左往右数"(",索引从1开始
- 第0组是字符串本身,m.Groups[0].Value==m.Value
- 匹配完成后使用Groups[index]即可访问所需的内容
示例代码如下:
public void TestGroup()
{
string regex = @"^(([0-9]+)([a-z]+))([0-9]+)$";
string input = "123abc456";
Match match = Regex.Match(input, regex);
for (int i = 0; i < match.Groups.Count; i++)
{
Console.WriteLine("match.Groups[" + i + "]=" +match.Groups[i].Value);
}
Console.ReadKey();
}
运行结果如图:
指定分组的名称
通过分组我们可以提取到匹配项,但是上面只讲了使用索引来提取指定的内容,如果分组很多的话索引值就不好数了,除此之外我们还可以使用(?< xxx>.+)为分组指定名称,xxx是指定的分组名称,.+是分组内容。
注意:指定名称的分组会排在未指定名称分组的后边
贪婪模式
正则表达式默认匹配尽可能多,示例代码如下:
public void TestGreed()
{
string input = "1234567890";
string regex = @"^(\d+)(\d+)(\d+)$";
Match match = Regex.Match(input, regex);
for (int i = 0; i < match.Groups.Count; i++)
{
Console.WriteLine("match.Groups[" + i + "]=" +match.Groups[i].Value);
}
Console.ReadKey();
}
运行结果如图:
默认匹配尽可能多,所以Groups[1]匹配了最多,只给Groups[2]、Groups[3]留了一个数字,留一个是因为Groups[2]、Groups[3]对应(\d+),+表示数量>=1,最小值是1,所以留了一个。
如果正则表达式变为string regex = @"^(\d+)(\d*)(\d*)$";,那Groups[2]、Groups[3]的值均为空,因为*表示数量>=0,最小值是0,所以一个不留。
运行结果如图:
?可以取消贪婪,取消第一个分组的贪婪模式后,第二个分组的权限会变得最大(即第二分组贪婪),string regex = @"^(\d+?)(\d+)(\d+)$";,运行结果如图:
一般在写正则表达式的时候,不需要去考虑贪婪的问题,直接写就行。如果出现了匹配结果比预期多的情况,一般是贪婪造成的,再去考虑取消贪婪的问题。
匹配一个整数
整数分为:0、正整数、负整数,不允许出现0123这种形式
包含匹配:“0|-?[1-9][0-9]*”,123abc456可以匹配到两个满足条件的结果——123和456,实际上123abc456不是正确的数字格式。
绝对匹配:"0|-?[1-9][0-9]*$",123abc456可以匹配到满足条件的结果456,因为"0|-?[1-9][0-9]*$“等价于”(0)|(-?[1-9][0-9]*$)",如果写成"(0|-?[1-9][0-9]*$)"那123abc456就匹配不到了
正则替换
public void TestReplace()
{
string input = "ab------c---d---ef----g";
string result = Regex.Replace(input, @"-+", "-");
Console.WriteLine("input=" + input);
Console.WriteLine("result=" + result);
}
运行结果如图:
引用匹配到的数据:
要引用前面匹配到的结果,可以使用$引用前面匹配到的部分,$0表示把前面匹配到的第0组拿过来,第0组是字符串本身,m.Groups[0].Value==m.Value,如果继续加"()"的话,可以用$1、$2…来引用
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Label1.Text = "http://www.baidu.com123 4 5 6http://www.csdn.net/";
}
}
protected void btnReplace_Click(object sender, EventArgs e)
{
string input = "http://www.baidu.com123 4 5 6http://www.csdn.net/";
//不加()
string result = Regex.Replace(input, @"http://[\w\.]+\.[a-z]+", "<a href=\"$0\">$0</a>");
//加上(),result==resultAdd
string resultAdd = Regex.Replace(input, @"(http://[\w\.]+\.[a-z]+)", "<a href=\"$1\">$1</a>");
Label1.Text = result;
}
点击替换按钮之前:
点击替换按钮之后:
注意
一般,正则表达式很难一次就写得完美,我们可以先写出来测试一下,然后根据匹配到的结果,逐步精细化正则表达式以达到自己的目的。
测试正则表达式可以使用RegexTester工具。
注意