一、正则表达式概述
1、正则表达式:用于操作字符串数据,通过一些特定的符号体现
2、String类中
(1)public boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。调用此方法的str.matches(regex)形式与以下表达式产生的结果完全相同:Pattern.matches(regex, str)
注:参数regex:具备一定规则的字符串,即用来匹配字符串的正则表达式
3、正则表达式虽然简化了书写,但阅读性变差了
二、正则表达式的符号
1、字符类
(1)[abc]:字符串的某一位上的字符,必须是:a或b或c
(2)[^abc]:除了a、b、c之外的任何字符
(3)[a-zA-Z]:a到z或A到Z,即 所有大小写字母
(4)[a-d[m-p]]:并集,a到d或m到p,即 [a-dm-p]
(5)[a-z&&[def]]:交集,a到z && d或e或f,即 [def]
(6)[a-z&&[^bc]]:交集,a到z && 非b到c, 即 [ad-z]
(7)[a-z&&[^m-p]]:交集,a到z && 非m到p,即 [a-lq-z]
2、预定义字符
(1).:任何字符
(2)\d:数字[0-9]
(3)\D:非数字[^0-9]
(4)\w:单词字符[a-zA-Z_0-9],即 数字、字母(含大小写)、下划线
(5)\W:非单词字符[^\w]
(6)\s:空白字符[ \t\n\x0B\f\r]
(7)\S:非空白字符[^\s]
3、边界匹配器
(1)^:行的开头
(2)$:行的结尾
(3)\b:单词边界(单词与单词之间的空格)
(4)\B:非单词边界
(5)\A:输入的开头
(6)\G:上一个匹配的结尾
(7)\Z:输入的结尾,仅用于最后的结束符(如果有的话)
(8)\z:输入的结尾
4、Greedy数量词
(1)X?:X,一次或一次也没有
(2)X*:X,零次或多次(有也行,没有也行)
(3)X+:X,一次或多次
(4)X{n}:X,恰好n次
(5)X{n, }:X,至少n次
(6)X{n, m}:X,至少n次,但是不超过m次
String str = "abcabcd";
String regex = "[abc]+d";
System.out.println(str.matches(regex)); //true
三、正则表达式对字符串的常见操作(正则的四个功能)
!!!匹配、切割、替换这些功能,底层用的都是Pattern对象的方法。因为正则模式作用于字符串只有一个对象:Pattern
eg:字符串中的matches(regex)方法,底层用的就是正则表达式中正则对象的matches()方法Pattern.matches(regex, str)
1、匹配:使用的是String类中的matches(regex)方法
public boolean matches(String regex)
//匹配手机号码是否正确
String phoneNum = "13522943355";
// String regex = "1[358][0-9]{9}";
// \d:表示[0-9]的数字
//反斜杠(\)在字符串中会自动对其后的字符进行转义,所以要使用\\d
String regex = "1[358]\\d{9}";
boolean result = phoneNum.matches(regex);
System.out.println(phoneNum + ":" + result);
2、切割:使用的是String类中的split(regex)方法
public String[] split(String regex)
//用空格切割姓名
String str = "zhangsan xiaoqiang zhaoliu";
String[] names = str.split(" ");
for (String name : names) {
System.out.println(name);
}
问:如果姓名之间有多个空格,怎么切割?
//用一个或多个空格切割姓名
String str = "zhangsan xiaoqiang zhaoliu";
// " +":匹配一个或多个空格
String[] names = str.split(" +");
for (String name : names) {
System.out.println(name);
}
//用 . 切割姓名
String str = "zhangsan.xiaoqiang.zhaoliu";
// ".":在正则表达式中是一个特殊符号,用 \\. 来转义
String[] names = str.split("\\.");
for (String name : names) {
System.out.println(name);
}
//用叠词切割姓名
String str = "zhangsantttttxiaoqiangmmmzhaoliu";
//()代表一个组,\\1代表与第一组相同
//(.)\\1+:有一个或多个与第一组相同的内容,而第一组是任意字符
String[] names = str.split("(.)\\1+");
for (String name : names) {
System.out.println(name);
}
分析:叠词,第二位的内容与第一位一致,意味着第一位的内容被后面复用。如何实现复用?
补充:
(1)正则中,复用的封装方式:小括号(Java中基本的复用形态是:封装成函数)。封装体没有名字,但会自动编号,从1开始。这种封装形式称为组
即 正则规则中用于封装的形式是小括号。为了能使用这种封装形式,没有名字,但有编号。写一个就是1编号,再写一个就是2编号
(2)(.):括号()就是组,该组的内容是 . ,即为任意字符。
\\1:使用第一组的内容。直接用编号n就代表使用第n组的内容,但编号n需要被转义成\\n
(.)\\1+:一个任意字符,后面有一个或多个与之相同的字符
(3)正则中的组按左括号来编号,有几个左括号就有几个组
eg:((A)(B(C))):有四个组,分别是:
1:((A)(B(C)))
2:\A
3:(B(C))
4:(C)
(4)正则中,封装成组来应用,提高规则的复用性
3、替换:使用的是String类中的replaceAll(regex, replacement)方法
public String replaceAll(String regex, String replacement)
//将叠词替换成#
String str = "zhangsanttttxiaoqiangmmmmmmmzhaoliu";
//使用replaceAll()方法,第一个参数是需要被替换的内容(正则表达式),第二个参数是替换后的内容
str = str.replaceAll("(.)\\1+", "#");
System.out.println("str = " + str); //str = zhangsan#xiaoqiang#zhaoliu
//将叠词替换成一个字符(eg:将多个t变成一个t,将多个m变成一个m)
String str = "zhangsanttttxiaoqiangmmmmmmmzhaoliu";
//$n:获取前一个参数中的第n组的内容
//$:在其他参数中可以对前一个参数中的已有的正则中的规则进行获取
str = str.replaceAll("(.)\\1+", "$1");
System.out.println("str = " + str); //str = zhangsantxiaoqiangmzhaoliu
补充:
(1)一个方法的第一个参数带着正则,第二个参数想使用第一个参数正则中的内容,用$n来表示
(2)$n:获取前一个参数中的第n组的内容
(3)$:在其他参数中可以对前一个参数中的已有的正则中的规则进行获取
//15800001111 --> 158****1111
String str = "15800001111";
//将号码分成三部分。$n是获取前一个参数中第n组的内容,所以 \\d{3} 要放在一个组中,用(\\d{3})
str = str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
System.out.println("str = " + str);
4、获取:使用匹配器Matcher中的group()方法
String group()
//获取str中由3个字母组成的单词
String str = "da jia hao , ming tian bu fang jia !";
//三个字母组成的单词,单词的两边要有单词边界\\b
String regex = "\\b[a-zA-Z]{3}\\b";
//将正则封装成对象
Pattern p = Pattern.compile(regex);
//通过正则对象获取匹配器对象
Matcher m = p.matcher(str);
//用Matcher对象的方法对字符串进行操作
//要获取三个字母组成的单词,先用find()查找,再用group()获取
while (m.find()) {
System.out.println(m.group());
//可以获取位置(包含头 不包含尾),相当于indexOf()
System.out.println(m.start() + "--" + m.end());
}
四、java.util.regex.Pattern
1、public final class Pattern:正则表达式的编译表示形式(需要将正则封装成对象)
2、指定为字符串的正则表达式必须首先被编译为此类的实例(将正则表达式封装成对象)。然后,可将得到的模式(正则规则)用于创建Matcher对象(匹配器),依照正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式
注:
(1)将正则封装成对象(封装对象,正则没有对字符串进行操作)
(2)建立一个匹配器对象
(3)正则只负责表达式的封装,要想用表达式操作字符串,必须要获取匹配器对象。匹配器对象用规则操作字符串,按规则对字符串进行匹配。匹配的结果都在匹配器中
//将正则规则进行对象的封装
Pattern p = Pattern.compile("a*b");
//通过正则对象的matcher()方法和字符串关联,获取要对字符串操作的匹配器对象Matcher
Matcher m = p.matcher("aaaaab");
//通过Matcher匹配器对象的方法对字符串进行操作
boolean b = m.matches();
3、方法
(1)static Pattern compile(String regex):将给定的正则表达式编译到模式中
(2)static Pattern compile(String regex, int flags):将给定的正则表达式编译到具有给定标志的模式中
(3)int flags():返回此模式的匹配标志
(4)Matcher matcher(CharSequence input):创建匹配给定输入与此模式的匹配器
(5)static boolean matches(String regex, CharSequence input):编译给定正则表达式并尝试将给定输入与其匹配
//以下两行代码的行为完全相同
Pattern.matches(regex, input); //便捷方式
Pattern.compile(regex).matcher(input).matches();
注:如果要多次使用一种模式,编译一次后重用此模式比每次都调用此方法效率更高
(6)String pattern():返回在其中编译过此模式的正则表达式
(7)static String quote(String s):返回指定String的字面值模式String。此方法产生一个String,可以将其用于创建与字符串s匹配的Pattern,就好像它是字面值模式一样。输入序列中的元字符和转义序列不具有任何特殊意义
(8)String[] split(CharSequence input):围绕此模式的匹配拆分给定输入序列
(9)String[] split(CharSequence input, int limit):围绕此模式的匹配拆分给定输入序列
(10)String toString():返回此模式的字符串表示形式。此为在其中编译过此模式的正则表达式
五、java.util.regex.Matcher
1、public final class Matcher:通过解释Pattern(正则表达式)对character sequence(字符序列)执行匹配操作的引擎(Matcher是真正用正则表达式操作字符串的对象)
2、通过调用模式的matcher()方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:
(1)matches()方法尝试将整个输入序列与该模式匹配
(2)lookingAt()尝试将输入序列从头开始与该模式匹配
(3)find()方法扫描输入序列以查找与该模式匹配的下一个子序列
3、部分方法
(1)boolean matches():尝试将整个区域与模式匹配。如果匹配成功,则可以通过start()、end()和group()方法获取更多信息
(2)int start():返回以前匹配的初始索引(返回第一个匹配字符的索引)
(3)int start(int group)
(4)int end():返回最后匹配字符之后的偏移量
(5)int end(int group)
(6)boolean find():尝试查找与该模式匹配的输入序列的下一个子序列(当且仅当输入序列的子序列匹配此匹配器的模式时才返回true)。此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始
(7)boolean find(int start)
(8)String group():返回由以前匹配操作所匹配的输入子序列。对于具有输入序列s的匹配器m,表达式m.group()和s.substring(m.start(), m.end())是等效的
(9)String group(int group)
(10)boolean lookingAt():尝试将从区域开头开始的输入序列与该模式匹配。与matches()方法类似,此方法始终从区域的开头开始;与之不同的是,它不需要匹配整个区域
4、find():查找,group():获取。group()获取之前要先find()查找,否则报错 IllegalStateException: No match found。所以,要先拿规则regex到字符串str中匹配。find()方法的返回值是boolean类型,想要获取全部,先要用while(find())循环,在循环体里面group()获取
//获取str中由3个字母组成的单词
String str = "da jia hao , ming tian bu fang jia !";
//三个字母组成的单词,单词的两边要有单词边界\\b
String regex = "\\b[a-zA-Z]{3}\\b";
//将正则封装成对象
Pattern p = Pattern.compile(regex);
//通过正则对象获取匹配器对象
Matcher m = p.matcher(str);
//用Matcher对象的方法对字符串进行操作
//要获取三个字母组成的单词,先用find()查找,再用group()获取
while (m.find()) {
System.out.println(m.group());
//可以获取字符串的位置(包含头 不包含尾),相当于indexOf()
System.out.println(m.start() + "--" + m.end());
}
5、匹配器Matcher也可以完成匹配、替换等操作,但字符串String中的匹配、替换等方法更为简单。但获取功能只能用匹配器Matcher中的方法完成
六、练习题
1、治疗口吃:我我我...要...要要...学学学学..编编...编..程....程程 --> 我要学编程
//治疗口吃:我我我...要...要要...学学学学..编编...编..程....程程 --> 我要学编程
//用替换
String str = "我我我...要...要要...学学学学..编编...编..程....程程";
//先将 . 都去掉。用\\.对.进行转义
str = str.replaceAll("\\.+", "");
System.out.println("str = " + str);
/**
* 去掉叠词
* (.):一组,该组中的内容为任意字符
* \\1:使用第一组的内容
* \\1+:使用第一组的内容一次或多次
* (.)\\1+:一个任意字符,后面有一个或多个与之相同的字符
* $1:使用第一个参数中第一组的内容
*/
str = str.replaceAll("(.)\\1+", "$1");
System.out.println("str = " + str);
2、对IP地址排序(按IP地址分类排序,每段数字越小越靠前):192.168.10.34 127.0.0.1 3.3.3.3 105.70.11.55
//对IP地址排序(按IP地址分类排序,每段数字越小越靠前):192.168.10.34 127.0.0.1 3.3.3.3 105.70.11.55
String str = "192.168.10.34 127.0.0.1 3.3.3.3 105.70.11.55";
/*
//将IP地址切出,用一个或多个空格切
String[] ips = str.split(" +");
*/
/**
* 使用TreeSet对象排序 -- 自动排序
* 问题:TreeSet默认按照字符串排序,而需求是按照数值大小排序
* 错误原因:每一段的位数不一样。如果每一段的位数相同,结果就是正确的
* 做法:前面补0,每段都加2个零(保证至少是3位),再截取(保留)后三位
*
* 即 为了让IP可以按照字符串顺序比较,只要让IP的每一段位数相同即可。所以,补0
* 按照每一位所需最多0进行补充,每一段都加2个0
*/
//每段都补2个0
str = str.replaceAll("(\\d+)", "00$1");
//每段保留后三位
str = str.replaceAll("0*(\\d{3})", "$1");
//将IP地址切出,用一个或多个空格切
String[] ips = str.split(" +");
//将切出的字符串放入TreeSet中自动排序
TreeSet<String> ts = new TreeSet<String>();
for (String ip : ips) {
ts.add(ip);
}
//遍历TreeSet取出其中的元素
for (String ip : ts) {
//将每段前面的0去掉
ip = ip.replaceAll("0*(\\d+)","$1");
System.out.println("ip = " + ip);
}
3、对邮件地址校验
//对邮件地址校验
//匹配规则
String mail = "abc1@sina.com.cn";
//是校验,不是注册,不用判断位数
// String regex = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]{1,3}){1,3}";
//笼统式匹配
String regex = "\\w+@\\w+(\\.\\w+)+";
boolean b = mail.matches(regex);
System.out.println("mail : " + b);
注:正则的阅读性差,而且写完后还需要不断地进行验证。所以,在实际开发中,会将正则的校验封装成方法,将需要校验的字符串作为参数传到方法中进行验证