首先引入基础正则表达式的基础语法
基础篇:
元字符
元字符是构造正则表达式的一种基本元素。
-
. :匹配除换行符以外的任意字符
-
\w:匹配字母或数字或下划线或汉字
-
\s:匹配任意的空白符
-
\d:匹配数字
-
\b:匹配单词的开始或结束
-
^:匹配字符串的开始
-
$:匹配字符串的结束
案例:
-
匹配8位数字的QQ号码:
^\d\d\d\d\d\d\d\d$
-
匹配1开头11位数字的手机号码:
^1\d\d\d\d\d\d\d\d\d\d$
重复限定符
正则表达式提供了对重复字符进行简写的方式:
-
*:重复零次或更多次
-
+:重复一次或更多次
-
?:重复零次或一次
-
{n}:重复n次
-
{n,}:重复n次或更多次
-
{n,m}:重复n到m次
有了这些限定符之后,我们就可以对之前的正则表达式进行改造了,比如:
-
匹配8位数字的QQ号码:
^\d{8}$
-
匹配1开头11位数字的手机号码:
^1\d{10}$
-
匹配银行卡号是14~18位的数字:
^\d{14,18}$
-
匹配以a开头的,0个或多个b结尾的字符串:
^ab*$
分组
限定符是作用在与他相邻的最左边的一个字符,那么问题来了,如果我想要ab同时被限定那怎么办呢?
正则表达式中用小括号()来做分组,也就是括号中的内容会作为一个整体。
如匹配字符串中包含0到多个ab开头:^(ab)*
转义
正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简答,就是在要转义的字符前面加个斜杠,也就是\即可。
条件
回到我们刚才的手机号匹配,我们都知道:国内号码都来自三大运营商,它们都有属于自己的号段。
比如联通有130/131/132/155/156/185/186/145/176等号段,假如让我们匹配一个联通的号码,那按照我们目前所学到的正则,应该无从下手的,因为这里包含了一些并列的条件,也就是“或”,那么在正则中是如何表示“或”的呢?
正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。
那么我们就可以用或条件来处理这个问题:
^(130|131|132|155|156|185|186|145|176)\d{8}$
区间
正则提供一个元字符中括号 [] 来表示区间条件。
-
限定0到9 可以写成[0-9]
-
限定A-Z 写成[A-Z]
-
限定某些数字 [165]
那上面的正则我们还改成这样:
^((13[0-2])|(15[56])|(18[5-6])|145|176)\d{8}$
反义
前面说到元字符的都是要匹配什么什么,当然如果你想反着来,不想匹配某些字符,正则也提供了一些常用的反义元字符:
元字符 | 解释 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
常用正则表达式
-
匹配中文字符的正则表达式:
[\u4e00-\u9fa5]
-
匹配Email地址的正则表达式:
^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$
匹配形式:2035346428@qq.com] -
匹配国内电话号码:
\d{3}-\d{8}|\d{4}-\d{7}
匹配形式:匹配形式如 0511-4405222 或 021-87888822 -
匹配腾讯QQ号:
[1-9][0-9]{4,}
匹配形式:2035346428 -
匹配身份证:
(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$
匹配形式:142228199108252125 -
匹配ip地址:
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
匹配形式:127.0.0.1 -
匹配国内的手机号:
^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$
匹配形式:1388888888
Java中正则表达式用法
java.util.regex
包主要包括以下三个类:
-
Pattern 类:
-
目的是传入要比较的内容
正则表达式的编译表示形式。若要使用正则表达式必须将其【编译到此类】的实例中。然后,可以使用生成的模式对象创建 Matcher 对象。
-
Matcher 类:
Matcher 对象是对输入字符串进行【解释和匹配】操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
-
PatternSyntaxException:
PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
下面是几个小案例
案例一:匹配一个名字
//用Test进行测试,这里也可以换成main方法
@Test
public void testRegex() {
//查找
String context = "i am xiaoHua, i com from HenNan.";
//正则表达式,意思为:用来匹配包含子串 "xiaoHua" 的任何字符串,
// 无论 "xiaoHua" 出现在该字符串的什么位置。
//其中 .*:开始可以有零个或多个任意字符
// .*:在 "xiaoHua" 之后可以再跟零个或多个任意字符。
String regex = ".*xiaoHua.*";
//拿到将要进行比较的regex
Pattern pattern = Pattern.compile(regex);
//让regex与context进行匹配
Matcher matcher = pattern.matcher(context);
//matcher.matches() 整个context的内容,是否有与regex匹配的
//是返回true 否则返回false
System.out.println("matcher.matches() = " + matcher.matches());
}
案例二:对文本内容进行替换
@Test
//替换算法
public void testAppendReplace() {
String regex = "xiaoHua";
String context = "My name is xiaoHua.xiaoHua need studay hard!";
String replacement = "xiaoHua";
//跟上面的操作一样
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(context);
//替换所有的xiaoHua
String result1 = m.replaceAll(replacement);
System.out.println(result1);
//替换第一个出现的xiaoHua
String result2 = m.replaceFirst(replacement);
System.out.println(result2);
}
案例三:匹配字符串并进行追加
@Test
public void AppendReplacement() {
String regex = "a*b";
String Input = "aabfooaabfooabfoobkkk";
//
String replacement = "-";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(Input);
//提前创建空字符串容器,然后把查找的结果放到这里
StringBuffer sb = new StringBuffer();
//改进方法是while循环,可以尝试写一下
// while (m.find()){
// m.appendReplacement(sb,replacement);
// }
//查找满足regx的文本
m.find();
m.appendReplacement(sb, replacement);
System.out.println(sb);
//接着之前的再次查找
m.find();
m.appendReplacement(sb, replacement);
System.out.println(sb);
//剩下的尾巴加到sb里面
m.appendTail(sb);
System.out.println(sb);
}
案例四:贪婪算法
@Test
//案例一
public void testGreed(){
//按照最多的进行匹配,首先找满足6个的。因此第一次满足的为123456,打印出来,之后再进行寻找“1”不满足最少的3个放弃。之后的每一次寻找都满足最大6个。打印结果为:
//匹配结果:123456
//匹配结果:123333
//匹配结果:123412
//匹配结果:333334
String regex = "\\d{3,6}";
String context = "1234561 123333 123412 333334";
System.out.println("regex = "+ regex);
System.out.println("context = "+ context);
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println("匹配结果:" + matcher.group());
}
}
//案例二,同时满足条件
public void testGreed2(){
//按照最多的进行匹配
//需要同时满足最大的6跟7.首先匹配“123456”,满足第一个要求,然后匹配“1234561”,满足第二个最大要求,进行打印。接下来匹配“123333”,满足第一个,不满足第二个,不打印。以此类推。
String regex = "\\d{3,6}\\d{4,7}";
String context = "1234561 123333 123412 333334";
System.out.println("regex = "+ regex);
System.out.println("context = "+ context);
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println("匹配结果:" + matcher.group());
}
}
案例五:懒惰匹配
@Test
//懒惰匹配语法(\\d{x,x+n}?),贪婪语法中括号后面加?.与贪婪匹配相反,它优先匹配最下的那一个。
public void lazyTest(){
//前面是惰性匹配,每次只匹配一个, 后面是贪婪匹配,一次匹配4个,第一次应该匹配五个
String regex = "(\\d{1,2}?)(\\d{3,4})";
//第一个满足前1后四,第二个不满足后面的最小3,第三个满足,第四个第五个不满足
String context = "242342 123 1233 23 3";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println(matcher.group(0));
}
}
进阶篇:
零宽断言
【断言】就是说正则可以【断定】在指定内容的前面或后面会出现满足指定规则的内容。
【零宽】 断言部分只确定位置不匹配任何内容,只是一种模式。内容宽度为零。
几个概念:
概念 | 功能 |
---|---|
预测/先行 | (模式在前),要求后面的符合匹配 |
回顾/后发 | (模式在后),要求前面的符合匹配 |
正 | 符合匹配 |
负 | 不符合匹配 |
正向先行断言
零宽度正预测先行断言
-
语法:(?=pattern)
-
作用:匹配pattern表达式的前面内容,不返回本身。
【正向先行断言】可以匹配表达式前面的内容,那意思就是(?=) 就可以匹配到前面的内容了。
如果我们要匹配所有内容那就是:
@Test
public void testAssert1(){
//.+: 这里的 . 是一个特殊字符,表示“任意字符”,而 + 表示“一个或多个”。因此,.+ 意味着“一个或多个任意字符”。这是贪婪匹配,意味着它将尽可能多地匹配字符。
//(?=</span>) 正预测先行断言
String regex = ".+(?=</span>)";
String context = "<span class=\"read-count\">阅读数:641</span>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println(matcher.group());
}
}
//匹配结果:<span class="read-count">阅读数:641
//如果只匹配数字 \d,那可以改成:
@Test
public void testAssert2(){
String regex = "\\d+(?=</span>)";
String context = "<span class=\"read-count\">阅读数:641</span>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println(matcher.group());
}
}
//匹配结果:
//641
正向后行断言
零宽度正回顾后发断言,断言在前,模式在后
-
语法:(?<=pattern)
-
作用:匹配pattern表达式的后面的内容,不返回本身。
有先行就有后行,先行是匹配前面的内容,那后行就是匹配后面的内容啦。
上面的例子,我们也可以用后行断言来处理:
@Test
public void testAssert3(){
String regex = "(?<=<span class=\"read-count\">阅读数:)\\d+";
String context = "<span class=\"read-count\">阅读数:641</span>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println(matcher.group());
}
}
负向先行断言
零宽度负预测先行断言
-
语法:(?!pattern)
-
作用:匹配非pattern表达式的前面内容,不返回本身。
有正向也有负向,负向在这里其实就是非的意思。
举个栗子:比如有一句 “我爱祖国,我是祖国的花朵”。现在要找到不是'的花朵'前面的祖国。
用正则就可以这样写:祖国(?!的花朵)
//负向先行断言
@Test
public void testAssert4(){
//输出的祖国是 我爱祖国的这个“祖国”
String regex = "祖国(?!的花朵)";
String context = "我是祖国的花朵,我爱祖国";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println(matcher.group());
}
}
负向后行断言
零宽度负回顾后发断言
-
语法:(?<!pattern)
-
作用:匹配非pattern表达式的后面内容,不返回本身。
举个例子:比如有一句 “我爱祖国,我是祖国的花朵”。现在要找到不是'我爱'后面的祖国。
用正则就可以这样写:(?<!我爱)祖国
@Test
public void testAssert5(){
String regex = "(?<!我爱)祖国";
String context = "我是祖国花朵,我爱祖国";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println(matcher.group());
}
}
捕获
语法:(exp)
解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第0组为整个表达式,第一组开始为分组。
-
比如固定电话的:020-85653333
-
正则表达式为:(0\d{2})-(\d{8})
按照左括号的顺序,这个表达式有如下分组:
序号 | 编号 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | 1 | (0\d{2}) | 020 |
2 | 0 | (\d{8}) | 85653333 |
@Test
public void testCatch(){
String context = "020-23454344";
String regex = "(0\\d{2})-(\\d{8})";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
for (int i = 0; i <= matcher.groupCount(); i++) {
System.out.println(matcher.group(i));
}
}
}
@Test
//命名编号捕获组
public void testCatch2(){
String context = "020-23454344";
String regex = "(?<areacode>0\\d{2})-(?<phone>\\d{8})";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
System.out.println("areacode = " + matcher.group("areacode"));
System.out.println("phone = " + matcher.group("phone"));
}
}
//输出结果
//areacode = 020
//phone = 23454344
非捕获组:
-
语法:(?:exp)
-
解释:和捕获组刚好相反,它用来标识那些不需要捕获的分组。
比如上面的正则表达式,程序不需要用到第一个分组,那就可以这样写:(?:0\\d{2})-(\\d{8})
序号 | 名称 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | 1 | (\d{8}) | 85653333 |
@Test
//非捕获组
public void testCatch3(){
String context = "020-23454344";
//0\d{2} 这个组将不再被捕获
String regex = "(?:0\\d{2})-(\\d{8})";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(context);
while (matcher.find()){
for (int i = 0; i <= matcher.groupCount(); i++) {
System.out.println(matcher.group(i));
}
}
}
作业:输入一行字符串,判断该字符串是否为邮箱
public class TestEmail {
public static void main(String[] args) {
//先弄一个输入框
Scanner scanner = new Scanner(System.in);
String email = scanner.next();
//判断是否为邮箱的正则表达式(网上有各种常用的表达式)
String regex = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(email);
boolean matches = matcher.matches();
if (matches){
System.out.println("您输入的是一个正确的邮箱!");
}else {
System.out.println("您输入的邮箱不正确!");
}
}
}