正则表达式是一串字符串,利用它可以方便地处理文本。
正则表达式中字符有两类:
1.普通字符:匹配其本身
2.元字符:有特殊含义的字符
1.语法:
1.1单个字符
语法 | 解释 |
\r \n \t | 回车符,换行符,制表符 |
\0n,\0nn,\0mn | 八进制表示的字符,比如:\0141,十进制就是97 |
\xhh(后跟两位字符) | 十六进制表示的字符,比如:\x6A,十进制就是106 |
\uhhhh(后跟四位字符) | Unicode编号表示字符 |
\x{h...h} | Unicode编号增码字符 |
1.2字符组
语法 | 解释 |
. | 默认模式下,它匹配出换行符以外的任意字符;在单行匹配模式下,匹配任意字符,包括换行符。 |
[abc] | 表示匹配abc中任意字符 |
[^abc] | “^”只有放在字符数组开头才是元字符,才表示排出的意思,表示,匹配除abc以外的任意字符 |
[0-9a-z] | “-”是一个元字符,表示连续多个字符;表示匹配 0到9、a到z的任意字符 |
[-0-9] | -放在“[”后面不在是一个元字符,表示匹配 -或 0-9的任意字符 |
[.*] | 除了"^、-、[]、\"外,其他元字符在数组中就是普通字符;表示匹配.或* |
[a-z&&[^de]] | 表示匹配a-z但不包括de |
[[abc][def]] | 匹配abcdef中的任意字符 |
\d | 表示digit,匹配一个数字字符,等同于[0-9] |
\w | 表示word,匹配一个单词字符,等同于[a-zA-Z0-9_](数字,字母下划线) |
\s | 表示空白,匹配一个空白字符,等同于[\t\n\x0B\f\r] |
\D | 匹配一个非数字字符,即[^\d]; |
\W | 匹配一个非单词字符,即[^\w]; |
\S |
匹配一个非空白字符,即[^\s]; |
1.3量词
语法 | 解释 |
x?、x?? | "?"是一个元字符,表示前面的字符可能出现,也可能不出现;表示:x出现0或1次;懒惰匹配 |
x*、x*? | "*"表示前面字符零次或多次出现;表示:x出现0或多次;加?表示懒惰匹配 |
x+、x+? | "+"表示前面字符一次或多次出现;表示:x出现1或多次;加?表示懒惰匹配 |
x{m,n}、x{m,n}? | x出现m到n次;加?表示懒惰匹配 |
x{m,}、x{m,}? | x出现m次或多次;加?表示懒惰匹配 |
x{n}、x{n}? | x出现n次;加?表示懒惰匹配 |
贪婪匹配:
例:表达式: a.*b去匹配aacabdsdsb时会匹配整个表达式;这就是贪婪匹配(默认是贪婪的)
懒惰匹配:
例:表达式: a.*?b去匹配aacabdsdsb时会匹配aacab;这就是懒惰匹配(加?表示懒惰匹配)
1.4分组
表达式用()括起来表示分组,分组可以嵌套:
比如表达式:
a(bc)((de)(fg))
第0个分组:abcdefg
第一个分组:bc;
第二个分组:defg
第三个分组:de
第四个分组:fg
然后\加分组编号就是引用之前匹配到的分组
对于上述例子中:\1,就是引用表达式bc;\2就是引用表达式defg;这就叫Back引用。
上述为通过分组编号引用分组,也可以通过对分组起名字(?<name>X),通过名字来引用分组(\k<name>):
例:
a(?<first>bc)(?<second>(?<third>de)(?<fourth>fg))
\k<first>:就是引用的bc
\k<second>:就是引用的defg;
说到这应该了解到,既然分了组还能引用,那说明内存中存有分组的文本;那这样就会带来很大的开销。
那么就有不将其放入内存的方法,(?:)就是不将分组捕获;
语法 | 解释 |
ab|cd | 表示匹配ac或cd |
(http|ftp|file) | 表示匹配 http或ftp或file |
a(bc)+d | 表示bc出现多次 |
<(\w+)>(.*)</\1> | (\w+)为第一个分组,\1表示对它的引用;这个表达式可以看成两个<>中的内容一致 |
(?<name>X) | 给分组X起名为name; |
\k<name> | 引用名为name的分组 |
(?:abc|def) | 匹配abc或def |
1.5特殊边界匹配
^、$、\A、\Z、\z、\b都是特殊边界元字符
语法 | 解释 |
^ | 在字符数组开始处表示排除;在字符数组外,表示字符串的开始;多行模式下匹配行开始比如:^[^abc]:表示不以a、b、c开始 |
$ | 匹配整个字符串的结束,如果字符串以换行符结束,$匹配的是换行符之前的边界;比如:abc$:表示匹配以abc或abc\r\n或abc\n结束; 多行模式下匹配行结束。 |
\A | 总是匹配整个字符串的开始边界 |
\Z | 总是匹配字符串的结束边界,如果以换行符结束,匹配换行符之前的边界; |
\z | 总是匹配字符串结束的边界 |
\b | 匹配的是单词边界,例如:\bboy\b 对于字符串”boyfirend“,只会匹配到boy。 |
1.6环视边界匹配
语法 | 解释 |
(?=...) | 要求右边的字符串为指定的表达式。比如:abc(?=def)要求c右边的表达式有def,比如abcdcf;abcd就不匹配 |
(?!...) | 要求右边的字符串不为指定的表达式。比如:abc(?!def)要求右边不是def的任何字符串 |
?<=... |
要求右边字符串为指定字符串。比如:(?<=def)abc要求左边是def; |
(?<!...) | 要求右边字符串不为指定字符串。 |
1.7转义与匹配模式
语法 | 解释 |
(?i) | 不区分大小写;比如:(?i)the可以匹配THE,也可以匹配The |
(?m) | 开启多行模式,^匹配行开始,$匹配行结束 |
(?s) | 开启单行模式,.匹配多行模式,包括换行符 |
\.\*\? | 转义,匹配 . * ? |
[.*^(){}] | 这些元字符在字符数组中就是普通字符,匹配.*^(){}中的任何一个 |
\\ | 转义,匹配\ |
\Q\E | \Q到\E之间的都视为普通字符 |
2.Java API:
正则表达式相关的类位于java.util.rege下,主要有两个类:
Pattern:表示正则表达式对象。
Matcher:将正则表达式应用于具体的字符串,通过它对字符串进行处理。
2.1表示正则表达式
划重点:正则表达式中的任何一个'\',在字符串中,需要替换为两个'\'
首先在Pattern类中:
public static final int DOTALL = 0x20;
public static final int MULTILINE = 0x08;
public static final int CASE_INSENSITIVE = 0x02;
public static final int LITERAL = 0x10;
这三个变量用来标识:单行模式,多行模式,忽略大小写模式,此模式下元字符将失去特殊意义。
将字符串编译成一个Pattern对象:
String regex = “abc(?=def)”;
Pattern pattern = Pattern.compile(regex);
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}
public static Pattern compile(String regex, int flags) {
return new Pattern(regex, flags);
}
上述代码中的flag就是对应上述的标识 。
Pattern还有一个静态方法:
public static String quote(String s) {
int slashEIndex = s.indexOf("\\E");
if (slashEIndex == -1)
return "\\Q" + s + "\\E";
StringBuilder sb = new StringBuilder(s.length() * 2);
sb.append("\\Q");
slashEIndex = 0;
int current = 0;
while ((slashEIndex = s.indexOf("\\E", current)) != -1) {
sb.append(s.substring(current, slashEIndex));
current = slashEIndex + 2;
sb.append("\\E\\\\E\\Q");
}
sb.append(s.substring(current, s.length()));
sb.append("\\E");
return sb.toString();
}
看到\\Q和\\E,应该基本猜出来quote方法就是将字符串中所有字符变为普通字符的作用。
2.2切分
public String[] split(CharSequence input)//参数类型是String,StringBuilder,StringBuffer的父类
这个方法与String类下面的split()类似。
但还是有些区别:
1.Pattern接收的参数更广泛;
2.如果split方法传递的参数中有元字符或者长度大于1,就会先将regex编译为Pattern对象,再调用Pattern对象的split方法。
2.3验证
验证输入的文本是否符合定义的正则表达式。
String中有方法:
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}
可以看出就是调用了Pattern中的matches方法。
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
就是先调用Pattern的compile方法创建一个Pattern对象,再调用matcher创建一个Matcher对象,再调用Matcher中的matches方法。
String regex = "\\d{10}";
String string = "0123456789";
System.out.println(string.matches(regex));
输出为true。
2.4查找
在Matcher方法中,有如下方法:
public boolean find()
public String group() {
return group(0);
}
public String group(int group)
find方法就是需要进行匹配的字符串的起始位置进行匹配,或者如果先前调用过此方法,那么就从该位置上进行继续查找。找到就返回true,之后就可以通过start,end,group方法获取更多信息;
public static void find(){
String regex ="\\d{4}-\\d{2}-\\d{2}";
Pattern pattern = Pattern.compile(regex);
String str = "today is 2018-11-12,yesterday is 2018-11-11";
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
System.out.println("find:"+matcher.group()+" "+"postion:"+matcher.start()+"-"+matcher.end());
}
}
find:2018-11-12 postion:9-19
find:2018-11-11 postion:33-43
2.5替换
我们都知道String下面有多个替换方法:
public String replace(char oldChar, char newChar)
public String replace(CharSequence target, CharSequence replacement)
可以看出第一个replace接收的是字符;第二个接收的的CharSequence类型;
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
replaceAll以及replaceFirst是将参数看作正则表达式;
replaceAll是替换所有找的字符串;
replaceFirst是替换第一个找到的;
上面的两个方法中的参数replacement还可以通过“$”进行捕获分组。例如:
String regx1 ="(\\d{4})-(\\d{2})-(\\d{2})";
String str1 = "today is 2018-11-12";
System.out.println(str1.replaceAll(regx1,"$1/$2/$3"));
输出为:today is 2018/11/12;
通过上面的源码我们可以看出是调用了Matcher中的replaceAll或replaceFirst方法。
3.常见表达式:
3.1邮编
邮编就是独立的六位数字:
(?<!\d)\d{6}(?!=\d);
验证结果:
public static final Pattern ZIP_REGX= Pattern.compile("(?<!\\d)\\d{6}(?!\\d)");
public static void findZipCode(String string){
Matcher matcher = ZIP_REGX.matcher(string);
while (matcher.find()){
System.out.println(matcher.group());
}
}
"我的邮编是725300,电话是18992580310"
725300
3.2电话号码
独立的十一位数字,以1开头,第二位取3、5、7、8、9(可能是这些吧),后面的数字无限制:
(?<!\d)1[3,5,7,8,9]\d{9}(?!\d)
验证:
public class PhoneCode {
public static final Pattern PHONE_NUMBER = Pattern.compile("(?<!\\d)1[3,5,7,8,9]\\d{9}(?!\\d)");
public static void findMobilePhone(String string){
Matcher matcher = PHONE_NUMBER.matcher(string);
while (matcher.find()){
System.out.println(matcher.group());
}
}
}
"我的邮编是725300,电话是18992580310"
18992580310
3.3时间和日期
时间一般就是
yyyy-mm-dd
(?!\d)\d{4}-(0[1-9] | 1[0-2])-(0[1-9] | [12]\d | 3[01]);
验证:
public class DataCode {
private static final Pattern DATA_NUMBER=Pattern.compile("(?<!\\d)\\d{4}-(0?[0-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])(?!\\d)");
public static void findData(String string){
Matcher matcher = DATA_NUMBER.matcher(string);
while (matcher.find()){
System.out.println(matcher.group());
}
}
}
今天是2018-11-12;2018-13-32这是一个错误的日期
2018-11-12