正则表达式约定特殊的字符和规则,使用一串特殊字符的排列表示出某一字符串的特征,是用于进行文本匹配的工具。
一、java.util.regex包中主要包含三个类:
1、Pattern类:正则表达式的编译表示形式。常用方法有:
1>static Pattern compile(String regex):将指定的正则表达式编译到模式中。
Pattern p = Pattern.compile("a*b");
2>Matcher matcher(String str):创建匹配指定字符串与本模式的匹配器。
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaaaaaab");
3>static boolean matches(String regex,String str):编译指定的正则表达式并与指定的字符串进行匹配。
Pattern.matches("a*b", "aaaaaaaaab");
4>String[] split(String str,int limit):使用编译到模式中的正则表达式分隔指定的字符串指定的次数。如果为负数或0次,则分隔次数不作限制。如果为0时丢弃尾部的空字符串。
Pattern p = Pattern.compile("\\|");
String[] strs = p.split("a|b|c|d",-3);
2、Matcher类:对输入字符串进行解释和匹配操作的引擎。需要调用Pattern对象的matcher方法来获得一个 Matcher对象。其有3种匹配方式:
1>matches():整个匹配,只有整个字符序列完全匹配成功,才返回true,否则返回false。如果本部分匹配成功,将移动到下次匹配的位置。
2>lookingAt():部分匹配,总是从第一个字符进行匹配,不管匹配成功与否都不再继续匹配。
3>find():部分匹配,从当前位置开始匹配,找到一个匹配的子串,将移动下次匹配的位置。
除了3种匹配方式外,常用的方法还有:
4>int start():返回第一次匹配字符之后的偏移量,也就是说调用此方法之前必须先调用匹配方法。
5>int end():返回最后匹配字符之后的偏移量,也就是说调用此方法之前必须先调用匹配方法。
6>String group(int i):返回由之前匹配操作所匹配的指定捕获组编号的子序列,也就是说调用此方法之前必须先调用匹配方法。
7>Matcher reset():重置匹配器。
8>String replaceAll(String replacement):将与给定正则表达式相匹配的每个子序列替换为指定的值。
3、PatternSyntaxException类:抛出未经检查的异常,表明正则表达式模式中的语法错误。
二、语法及规则约定:
1、表示字符的方式:
1>转义字符:在正则表达式中,有些字符表示特殊的含义,如果需要匹配这些字符本身,则不能再使用这些字符,而必须通过处理才能使用,否则它将是正则表达式中表示的特珠含义。
字符 | 正则表达式中的含义 | 处理方式 |
^ | 单独使用时表示匹配输入字符串的开始位置,如果放在[]中表示否定 | \\^ |
$ | 匹配输入字符串的结尾位置,如果设置了RegExp对象的Multiline属性,则$也匹配‘\n'或‘\r' | \\$ |
() | 表示捕获组的开始和结束 | \\( \\) |
[] | []中指定的多个字符中的任意一个,或者指定一个范围 | \\[ \\] |
{} | 限定某部分出现的次数 | \\{ \\} |
. | 除换行符以外的任意一个字符 | \\. |
\ | 用在转义字符前可匹配特殊字符,用在捕获组编号前表示之前所匹配的指定编号的组,用在0xxx之前表示八进制表示的字符。由于"\"比较特殊,在String中也需要转义处理,所以String中的\是"\\",而正则匹配时则需要写成\\\\ | \\\\ |
| | 或者,两项之间的任一个 | \\| |
? | 跟在字符后表示该字符不出现或出现1次,跟在数量限定符后表示非贪婪限制符 | \\? |
+ | 出现1次或多次 | \\+ |
* | 不出现或出现多次 | \\* |
2>预定义字符:使用正则表达式中已事先定义的字符表示特定的值:
字符 | 表示含义 | 等价表示 |
\\d | 数字 | [0-9] |
\\D | 非数字 | [^0-9] |
\\s | 空白字符 |
|
\\S | 非空白字符 | [^\\s] |
\\w | 单词字符 | [a-zA-Z_0-9] |
\\W | 非单词字符 | [^\\w] |
3>字符缩略表示:有些字符比较抽象,比如换行符等,可以用特殊的字符或者Unicode转义表示:
含义 | 表示 | Unicode转义表示 |
制表符 | \t | \u0009 |
换行符 | \n | \u000A |
回车符 | \r | \u000D |
换页符 | \f | \u000C |
报警符 | \a | \u0007 |
转义符 | \e | \u001B |
垂直制表符 | \v | \u000B |
4>进制表示:使用八进制或十六进制表示字符:
①八进制表示:格式可以是\0n,\0nn或者\0mnn,其中0<=n<=7,0<=m<=3。
②十六进制表示:格式只能是\xhh,其中h只能在0-9、a-f或者A-F之间。
5>Unicode转义表示:格式是\uhhhh,其中h只能在0-9、a-f或者A-F之间。
6>指定匹配某个固定字符:直接将该字符放入正则表达式即可。
boolean b = Pattern.matches("A","AB");
7>指定匹配某几个字符里的任意一个:
①使用中括号[]将可选的字符括住即可。
boolean b = Pattern.matches("[ABC]","A");
②使用|
boolean b = Pattern.matches("A|B","A");
8>指定匹配某个连续范围的某个字符:在[]中用-指定一个范围
boolean b = Pattern.matches("[A-Za-z]","A");
9>指定匹配某个范围的字符并且匹配另一个范围的字符:两个范围使用&&连接并放在[]中
boolean b = Pattern.matches("[[a-z]&&[abcde]]","a");
10>指定匹配不是某几个字符:在不是的字符前加^并放在[]中
boolean b = Pattern.matches("[^a-c]", "b");
2、表示字符出现的次数:
1>字符不出现或出现1次:?
boolean b = Pattern.matches("ab?c", "ac");
2>字符出现1次或多次:+
boolean b = Pattern.matches("ab+c", "abbbc");
3>字符出现0次或多次:*
boolean b = Pattern.matches("ab*c", "ac");
4>字符出现指定的次数:使用{}将次数括起来,比如字符出现3次。
boolean b = Pattern.matches("ab{3}c", "abbbc");
5>字符出现的次数是一个范围:使用{}将次数括起来,最小值和最大值用,隔开,比如字符最少出现3次,最多出现7次
boolean b = Pattern.matches("ab{3,7}c", "abbbbbbbc");
6>字符出现最少的指定次数:使用{}将最少次数括起来,去掉最大次数即可。比如字符最少出现3次
boolean b = Pattern.matches("ab{3,}c", "abbbc");
三、捕获组:
捕获组就是把正则表达式中子表达式匹配的内容,保存到以数字编号的内存中或保存到显式命名的组里,方便后面引用。这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。捕获组用括号表示。
1、编号规则:按照“(”出现的顺序,从左到右,从1开始进行编号的。特殊地,编号为0的捕获组,指的是正则表达式整体。
2、命名捕获组的命名规则:通过显式命名,可以通过组名方便的访问到指定的组,而不需要去一个个的数编号。同时避免了在正则表达式扩展过程中,捕获组的增加或减少对捕获组编号的变化导致的不可控情况。给捕获组命名的方式是:在要命名的组所在的括号中将”?<别名>“放在该组的正则表达式之前,此时<>中的名字即为该组的别名。比如给一个匹配格式为yyyy-MM-dd的日期的正则表达式进行捕获组命名,此时别名分别是year、date、month和day:
(?<year>\\d{4})-(?<date>(?<month>\\d{2})-(?<day>\\d\\d))
3、在正则表达式内部使用捕获组(即反向引用):在要使用的捕获组之后使用\\加捕获组编号或者\\k<捕获组名>,即可将捕获组匹配的字符串作为正则表达式的一部分使用。比如:
Pattern p = Pattern.compile("(\\d{4})-((?<month>\\d{2})-(?<day>\\d\\d))-\\3");
Matcher m = p.matcher("2017-03-31-04");
System.out.println(m.matches());
或者:
Pattern p = Pattern.compile("(\\d{4})-((?<month>\\d{2})-(?<day>\\d\\d))-\\k<month>");
Matcher m = p.matcher("2017-03-31-04");
System.out.println(m.matches());
的结果返回false,因为正则表达式的最后部分使用了反向引用,即需要匹配之前匹配到的捕获组编号是3(别名是month)的内容(即字符串“2017-03-31-04”中的03)。所以:
Pattern p = Pattern.compile("(\\d{4})-((?<month>\\d{2})-(?<day>\\d\\d))-\\3");
Matcher m = p.matcher("2017-03-31-03");
System.out.println(m.matches());
或者:
Pattern p = Pattern.compile("(\\d{4})-((?<month>\\d{2})-(?<day>\\d\\d))-\\k<month>");
Matcher m = p.matcher("2017-03-31-03");
System.out.println(m.matches());
才会返回true。
四、匹配模式:当在字符后跟数量限定时(比如字符后跟?、+、*或者{2,5}),会涉及到匹配模式的问题。
1、贪婪模式(Greediness):即最大匹配,是匹配模式的默认匹配方式。也就是说在数量限定词后不加匹配模式限定符时采用的是贪婪模式。例如:
Pattern p = Pattern.compile("a.*d");
Matcher m = p.matcher("a1bcda2bcda3bc");
boolean b = m.find();
System.out.println(b);
System.out.println(m.group());
此时会尝试匹配最多的字符,如果更多的字符不匹配时会返回上次匹配的字符,所以匹配的字符串是a1bcda2bcd而不是a1bcd。
2、懒惰模式(Laziness):也叫勉强模式(Reluctant),即最小匹配。需要在数量限定词后加?即成为该模式。例如:
Pattern p = Pattern.compile("a.*?d");
Matcher m = p.matcher("a1bcda2bcda3bc");
boolean b = m.find();
System.out.println(b);
System.out.println(m.group());
此时会匹配最少的字符,只要找到即可,所以匹配的字符串是a1bcd而不a1bcda2bcd。
3、占有模式(Possessive):即完全匹配。需要在数量限定词后加+即成为该模式。例如:
Pattern p = Pattern.compile("a.*+d");
Matcher m = p.matcher("a1bcda2bcda3bcd");
boolean b = m.find();
System.out.println(b);
System.out.println(m.group());
此时无法匹配,因为正则中a之后的.*已将字符串中a之后包括最后一位最后一位d在内的剩余的所有字符串都匹配了,而正则最后还有一个d,此时字符串中已无字符,也无法回退匹配相对少的子字符串,所以不能匹配。由此可以看出在占有模式中,正则中的?+或*+之后不能再指定某一明确固定的字符,否则将永远不匹配。
五、非捕获组(零宽断言):之所以是零宽,是因为它们像^和$一样匹配的只是位置,而不是字符。
1、正向先行断言:(?=pattern)表示该位置之后的字符能够匹配pattern,比如字符串a regular expression,想要匹配regular中的re而不是expression中的re,可以使用re(?=gular),表明限定re右边必须是gular。如果使用正则:re(?=gular).则返回reg。因为断言不会匹配字符,它仅仅是一个位置。
2、负向先行断言:(?!pattern)表示该位置之后的字符不能匹配pattern,比如字符串regex represents regular expression,想要匹配除regex和regular之外的re,可以使用re(?!g),表明限定re右边不能是g。负向和正向的区别,就在于该位置之后的字符能否匹配括号中的表达式。
3、正向后行断言:(?<=pattern)表示该位置之前的字符能够匹配pattern,比如字符串regex represents regular expression,想要匹配单词内部的re,但不匹配单词开头的re,可以使用(?<=\w)re。后行和先行的区别在于,后行是从右往左扫描而先行是从左往右扫描。
4、负向后行断言:(?<!pattern)表示该位置之前的字符不能匹配pattern,比如字符串regex represents regular expression,想要匹配单词开头的re,可以使用(?<!\w)re。