【JAVA入门】Day20 - 正则表达式
文章目录
- [【JAVA入门】Day20 - 正则表达式](#【JAVA入门】Day20 - 正则表达式)
-
- 一、正则表达式使用的注意事项
-
- [1.1 一个 [ ] 匹配一个字符](#1.1 一个 [ ] 匹配一个字符)
- [1.2 表示"或者"的表达式可以再用一个 [ ] 括起来](#1.2 表示“或者”的表达式可以再用一个 [ ] 括起来)
- [1.3 &&表示"而且"](#1.3 &&表示“而且”)
- [1.4 ^表示"非"](#1.4 ^表示“非”)
- 二、预定义字符(只能匹配一个字符)
-
- [2.1 转义字符](#2.1 转义字符)
- [2.2 . 表示任意字符](#2.2 . 表示任意字符)
- [2.3 \d 表示任意一位数字](#2.3 \d 表示任意一位数字)
- [2.4 \w 表示只能是一位单词字符 [a-zA-Z_0-9]](#2.4 \w 表示只能是一位单词字符 [a-zA-Z_0-9])
- 三、数量词
- 四、正则表达式的使用
- 五、正则表达式中用到的符号
- 六、爬虫
-
- [6.1 爬取本地信息](#6.1 爬取本地信息)
- [6.2 爬取网络里的数据](#6.2 爬取网络里的数据)
- 七、使用正则表达式进行带条件的爬取
- 八、贪婪爬取和非贪婪爬取
- 九、正则表达式在字符串方法中的使用
- 十、分组
-
- [10.1 捕获分组](#10.1 捕获分组)
- [10.2 非捕获分组](#10.2 非捕获分组)
正则表达式可以校验字符串是否满足一定的规则,并用来校验数据格式的合法性。利用正则表达式校验字符串,可以省去大量代码。
java
package Regex;
public class RegexDemo1 {
public static void main(String[] args) {
/* 假如现在要求校验一个QQ号码是否正确。
规则:6位至20位以内,0不能在开头,必须全部是数字
*/
String qq = "1234567890";
//利用方法
boolean result1 = checkQQ(qq);
//利用正则表达式
boolean result2 = qq.matches("[1-9]\\d{5,19}");
System.out.println(result1);
System.out.println(result2);
}
public static boolean checkQQ(String qq) {
//先把异常数据过滤,剩下的就是满足要求的数据
int len = qq.length();
if(len < 6 || len > 20) {
return false;
}
if(qq.startsWith("0")) {
return false;
}
for (int i = 0; i < qq.length(); i++) {
char c = qq.charAt(i);
if(c < '0' || c > '9') {
return false;
}
}
return true;
}
}
正则表达式的作用主要有两点:
- 作用一:校验字符串是否满足规则。
- 作用二:在一段文本中查找满足要求的内容。
正则表达式的使用规则比较复杂,可以参考下面的图片:
一、正则表达式使用的注意事项
1.1 一个 [ ] 匹配一个字符
需要注意的是,一个[ ]内部只能匹配一个字符:
java
System.out.println("ab".matches("[abc]")); //false
System.out.println("ab".matches("[abc][abc]")); //true
一个中括号只能管得了一个字符,它们是逐个字符匹配的。若想匹配多个字符,就要写多个中括号。
java
System.out.println("aa".matches("[a-zA-Z]")); //false
System.out.println("aa".matches("[a-zA-Z][a-zA-Z]")); //true
1.2 表示"或者"的表达式可以再用一个 [ ] 括起来
为了增强可读性,二者写法等价,表示 "或者":
java
System.out.println("a".matches("[a-dm-p]")); //true
System.out.println("a".matches("[a-d[m-p]]")); //true
1.3 &&表示"而且"
如果想求两个范围的交集,一定要写两个 & 。否则会被识别为单个'&'符号。
java
System.out.println("a".matches("[a-z&&[def]]")); //false
System.out.println("e".matches("[a-z&&[def]]")); //true
System.out.println("&".matches("[a-z&[def]]")); //true &被识别为单个符号
1.4 ^表示"非"
^bc,表示除了bc。 ^m-p 表示除了m到p。
java
System.out.println("a".matches("[a-z&&[^bc]]")); //true
System.out.println("b".matches("[a-z&&[^bc]]")); //false 等同于[ad-z]
System.out.println("a".matches("[a-z&&[^m-p]]")); //true
System.out.println("m".matches("[a-z&&[^m-p]]")); //false 等同于[a-lq-z]
二、预定义字符(只能匹配一个字符)
java
. 任何字符
\d 一个数字:[0-9]
\D 一个非数字:[^0-9]
\s 一个空白字符:[\t\n\x0B\f\r]
\S 一个非空白字符[^\s]
\w 英文、数字、下划线[a-zA-Z_0-9]
\W 一个非单词字符[^\w]
2.1 转义字符
java
\ 是一个转义字符,用来改变后面跟的那个字符原本的含义
java
System.out.println("\""); //"
System.out.println("\\"); //\
2.2 . 表示任意字符
java
System.out.println("你".matches(".")); //true
System.out.println("你a".matches("..")); //true
2.3 \d 表示任意一位数字
注意:\d 才是表示任意一位数字,在字符串中需要先用 双杠 把 \ 转换为其本来的意思。
java
System.out.println("a".matches("\\d")); //false
System.out.println("3".matches("\\d")); //true
System.out.println("333".matches("\\d")); //false
System.out.println("333".matches("\\d\\d\\d")); //true
2.4 \w 表示只能是一位单词字符 [a-zA-Z_0-9]
java
System.out.println("_".matches("\\w")); //true
System.out.println("你".matches("\\w")); //false
System.out.println("2".matches("\\w")); //true
三、数量词
如果想要让一个正则表达式出现多次,可以用数量词修饰。
java
//必须是数字、字母、下划线,至少6位
System.out.println("2442fsfsf".matches("\\w{6,}")); //true \w表示必须是数字、字母、下划线,{6,}表示至少有6个字符
System.out.println("244f".matches("\\w{6,}")); //false
//必须是数字和字符,必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}")); //true
System.out.println("23_F".matches("[a-zA-Z0-9]{4}")); //false
System.out.println("23dF".matches("[\\w&&[^_]]{4}")); //true
System.out.println("23_F".matches("[\\w&&[^_]]{4}")); //false
四、正则表达式的使用
【练习1】需求:利用正则表达式,验证用户输入的:手机号、邮箱号、电话号码,是否满足要求。
java
package Regex;
public class RegexDemo3 {
public static void main(String[] args) {
/*
验证手机号码 13112345678 13712345667
验证座机电话号码 020-2324242 02122442 027-32323
验证邮箱号码 3232323@qq.com zhangsna@itcast.cnn
*/
//心得:
//拿着一个正确的数据,从左到右依次去写
//13112345678
String regex1 = "1[3-9]\\d{9}"; // \d{9}表示任意数字可以出现9次,也只能出现9次
//座机号码
//020-2324242 02122442 0712-3242434
//一:区号,开头是0,\d{2,3}表示任意的数字,且必须出现2~3次
//二:-,可以出现,也可以不出现,但出现至多只有一次,因此是零次或一次
//三:号码,不能以0开头,从第二位开始可以是任意数字。号码的总长度:5~10位
String regex2 = "0\\d{2,3}-?[1-9]\\d{4,9}";
//邮箱号码
//3232323@qq.com zhangsna@itcast.cnn dlei0009@163.com dlei0009@pci.com.cn
//第一部分:@的左边,至少出现一次,即一次或多次
//第二部分:@,只能出现一次
//第三部分A: .的左边,可以是字母或数字,但是没有下划线,大概有2~6位
//第三部分B: .
//第三部分C: .的右边,大写字母和小写字母都可以,只能出现2~3次
//第三部分D: pci.com.cn这里出现了多次.XXX,可以把.com和.cn看成一组.XXX结构出现了两次
String regex3 = "\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}";
}
}
【练习2】24小时的正则表达式。
java
//00:00:00
//12:20:30
//第一个:前
//第一位是0或1时,此时第二位可以是任意数字
//中间用"或"
//第一位是2时,此时第二位只能是0~3
//第二个:前
//分钟的第一位可以是0~5,第二位可以是任意数字
//第二个:后
//秒钟的第一位可以是0~5,第二位可以是任意数字
String regex4 = "([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";
//可以用()分组优化掉后面的分钟和秒钟,直接出现两次
String regex5 = "([01]\\d|2[0-3])(:[0-5]\\d){2}";
【练习3】写用户名和身份证的正则表达式。
java
/*
验证用户名是否满足要求:大小写字母,数字,下划线一共4-16位
验证身份证号码是否满足要求。
简单要求:18位,前17位任意数字,最后一位可以是大小写的X。
复杂要求:按照身份证号码的格式严格要求。
*/
//用户名
String regex6 = "\\w{4,16}";
//身份证号码的简单校验:身份证号一共18位
//第一位一定不是0,是1~9
//之后16位都是任意数字
//最后一位可以是大小写的X
String regex7 = "[1-9]\\d{16}(X|x|\\d)";
String regex8 = "[1-9]\\d{16}[\\dXx]";
String regex8_1 = "[1-9]\\d{16}(\\d|(?i)x)";
//忽略大小写的书写方式
//在匹配的时候,忽略abc的大小写
String regex9 = "(?i)abc";
//在匹配的时候,忽略bc的大小写
String regex10 = "a(?i)bc";
//在匹配的时候,忽略b的大小写
String regex11 = "a((?i)b)c";
//复杂身份证的检验
//410801 1993 02 28 457x
//前6位:省份,市区,派出所等信息,第一位不是0,后5位是任意数字
//年的前半段:18 19 20
//年的后半段:任意数字*2
//月份: 01 ~ 09 10 11 12
//日期: 01 ~ 09 10 ~ 19 20 ~ 29 30 ~ 31
//后四位: 任意数字出现3次 最后一位可以是数字,可以是大小写X
String regex12 = "[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}(\\d|(?i)x)";
System.out.println("41080119930228457x".matches(regex12));
这里出现了一种新的正则,表示忽略大小写,用(?i)表示,他放在谁前面,就表示忽略后面字符的大小写。关于类似的正则还有很多,没法介绍全,可以查看API手册。
五、正则表达式中用到的符号
注意方括号和圆括号的区分,方括号表示里面的内容出现一次,永远是单个字符;圆括号表示里面的内容进行分组,和数学运算里的含义类似。
还要注意"且"要用两个&表示;"或"用一个|表示,表示并集,如果写在方括号里面,可以省略;"非"用^表示。
六、爬虫
正则表达式另一个作用是,在一段文本中查找满足要求的内容,这就是爬虫的概念。
6.1 爬取本地信息
如下,我们利用 正则表达式 Pattern 和 文本匹配器 Matcher 找到了所有 JavaXX 格式的文本子串。
java
public class Spider {
public static void main(String[] args) {
String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会" +
"逐渐登上历史舞台";
//1.获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//2.获取一个文本匹配器
Matcher m = p.matcher(str);
//3.利用循环读取
while(m.find()) {
String s = m.group();
System.out.println(s);
}
}
6.2 爬取网络里的数据
爬取网络中的数据,需要用URL对象连接网页,然后用一个BufferedReader对象去逐行读取。之后针对每行按照正则表达式使用Matcher匹配出想要的内容即可。
java
package Regex;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Spider2 {
public static void main(String[] args) throws IOException {
/*
把链接:https://www.leuai.cn/idcard/
中所有的身份证号码爬取出来。
*/
//创建一个URL对象
URL url = new URL("https://www.leuai.cn/idcard/");
//连接上这个网址
URLConnection conn = url.openConnection();
//创建一个对象去读取网络中的数据
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
//创建一个正则表达式对象
String regex = "[1-9]\\d{17}";
Pattern pattern = Pattern.compile(regex);
String line;
//在读取的时候每次读一整行
while((line = br.readLine()) != null) {
//对每一行进行文本匹配
Matcher matcher = pattern.matcher(line);
//使用matcher找到所有满足身份证格式的子串
while(matcher.find()){
System.out.println(matcher.group());
}
}
br.close();
}
}
【练习】利用正则表达式爬取文本中的座机号码、邮箱、手机号、热线号码。
java
package Regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo4 {
public static void main(String[] args) {
/*
手机号:18512516758 18512508907
邮箱:boniu@itcast.cn
座机电话:01036517895 010-98951256
邮箱:bozai@itcast.cn
热线电话:400-618-9090 400-618-4000 4006184000 4006189090
手机号正则:1[3-9]\d{9}
邮箱正则:\w+@[\w&&[^_]]{2,6}(\.[a-zA-Z]{2,3}){1,2}
座机电话:0\d{2,3}-?[1-9]\d{4,9}
热线电话:400-?[1-9]\\d{2}-?[1-9]\\d{3}
*/
String s = " 手机号:18512516758 18512508907\n" +
" 邮箱:boniu@itcast.cn\n" +
" 座机电话:01036517895 010-98951256\n" +
" 邮箱:bozai@itcast.cn\n" +
" 热线电话:400-618-9090 400-618-4000 4006184000 4006189090";
String regex = "(1[3-9]\\d{9})|(\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2})|(0\\d{2,3}-?[1-9]\\d{4,9})|(400-?[1-9]\\\\d{2}-?[1-9]\\\\d{3})";
//1.获取正则表达式对象
Pattern p = Pattern.compile(regex);
//2.获取Matcher对象
Matcher m = p.matcher(s);
//3.逐行匹配
while(m.find()){
System.out.println(m.group());
}
}
}
七、使用正则表达式进行带条件的爬取
使用正则表达式爬取数据时,我们有时需要一些限制条件。
java
package Regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo5 {
public static void main(String[] args) {
/*
Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来
不久Java17也会逐渐登上历史舞台
*/
/*
需求1:爬取版本号为8,11,17的Java文本,但是只要Java,不显示版本号。
需求2:爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17。
需求3:爬取除了版本号为8,11,17的Java文本。正确爬取结果为:Java。
*/
String s = "java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和JAva11,因为这两个是长期支持版本,下一个长期支持版本是JAVa17,相信在未来\n" +
" 不久JAVA17也会逐渐登上历史舞台";
//需求1.定义正则表达式
//?是占位符,理解为前面的数据Java
//=表示在Java后面要跟随的数据要满足的要求
//但是在获取的时候,只获取前半部分
String regex1 = "((?i)java)(?=8|11|17)";
//需求2.加上这些数字
//?是占位符,理解为前面的数据Java
//:表示在Java后面要跟随的数据要满足的要求
//但是在获取的时候,也带上后半部分
String regex2 = "((?i)java)(?:8|11|17)";
//需求3.选取第一个不带版本号的java
//?是占位符,理解为前面的数据Java
//!表示在Java后面要跟随的数据要满足的要求,但是这个!表示不要,意思是"不要"后面有这些要求的内容
String regex3 = "((?i)java)(?!8|11|17)";
//1.获取正则表达式对象
Pattern p = Pattern.compile(regex1);
//2.获取Matcher对象
Matcher m = p.matcher(s);
//3.逐行匹配
while(m.find()){
System.out.println(m.group());
}
}
}
?作为占位符,指代前面的"java",?后面的符号决定了以什么条件爬取。
- ?= 爬取满足后面"条件"的子串,但是获取时不获取等号后面的"条件"
- ?: 爬取满足后面"条件"的子串,而且获取时也带上后面的"条件"
- ?! 爬取不满足后面"条件"的子串,获取时不带上后面的"条件"
八、贪婪爬取和非贪婪爬取
这是两种不同的爬取策略。
java
/*
Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaa
经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来
不久Java17也会逐渐登上历史舞台
*/
需求1:按照ab+的方式爬取ab,b尽可能多获取。
需求2:按照ab+的方式爬取ab,b尽可能少获取。
解释:ab+表示a出现1次,b出现1次或多次,b可以只出现1次,也可以全部出现。
贪婪爬取:abbbbbbbbbbbb。
非贪婪爬取:ab。
在 Java 当中,默认的就是贪婪爬取,如果我们在数量词 + * 的后面加上问号,此时就是非贪婪爬取。
java
package Regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo6 {
public static void main(String[] args) {
/*
Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaa
经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来
不久Java17也会逐渐登上历史舞台
*/
/*
只写+和*表示贪婪爬取
+? 非贪婪爬取
*? 非贪婪爬取
*/
String s = " Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaa\n" +
" 经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来\n" +
" 不久Java17也会逐渐登上历史舞台";
//贪婪爬取,+表示1次或多次
String regex1 = "ab+";
Pattern p = Pattern.compile(regex1);
Matcher m = p.matcher(s);
while(m.find()) {
System.out.println(m.group()); //abbbbbbbbbbbb
}
//非贪婪爬取
String regex2 = "ab+?";
Pattern pattern = Pattern.compile(regex2);
Matcher matcher = pattern.matcher(s);
while(matcher.find()) {
System.out.println(matcher.group()); //ab
}
}
}
九、正则表达式在字符串方法中的使用
String 类中还有几个正则表达式的相关方法:
方法名 | 说明 |
---|---|
public boolean matches(String regex) | 判断字符串是否满足正则表达式的规则 |
public String replaceAll(String regex,String newStr | 按照正则表达式的规则进行替换 |
public String[] split(String regex) | 按照正则表达式的规则切割字符串 |
【练习】使用以上方法操作字符串。
java
package Regex;
public class RegexDemo7 {
public static void main(String[] args) {
/*
有一段字符串:小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠
要求1:把字符串中三个姓名之间的字母替换为vs。
要求2:把字符串中的三个姓名切割出来。
*/
String s = "小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠";
String regex = "[\\w&&[^_]]+";
//把满足正则表达式的内容替换为vs
String result1 = s.replaceAll(regex,"vs");
System.out.println(result1);
//利用正则表达式作为分隔符,切割字符串
String[] result2 = s.split(regex);
for(int i = 0; i < result2.length; i++) {
System.out.println(result2[i]);
}
}
}
十、分组
正则表达式中的分组刚才已经介绍过,其实就是小括号。
由于有"或"、"且"等逻辑符号的存在,分组在正则表达式中的编写是必须存在的,否则去掉括号的话下面的正则表达式就会改变原意(或的范围变化)。
java
String regex = "[1-9]\\d{16}(\\d|X|x)";
分组的另一个用途则是可以简化代码。
java
//24小时制的正则表达式
String regex1 = "([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";
String regex2 = "([01]\\d|2[0-3]):([0-5]\\d){2}";
正则表达式中的分组,其实是有组号的,也就是分组的序号,规则如下:
- 规则1:从1开始,连续不间断。
- 规则2:以左括号为基准,最左边的左括号是第一组,其次为第二组,以此类推。
java
(\\d+)(\\d+)(\\d+)
//第一组 第二组 第三组
(\\d+(\\d+))(\\d+)
//第一组 第二组 第三组
分组编号的意义在于捕获。
10.1 捕获分组
捕获分组就是把这一组的数据捕获出来,再用一次。看下面的例子。
【练习】需求1:判断一个字符串的开始和结束字符是否一致?只考虑一个字符。
a123a b456b 17891 &abc&
需求2:判断一个字符串的开始部分和结束部分的多个字符是否一致。
abc123abc b456b 123789123 &!@abc&!@
需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致。
aaa123aaa bbb456bbb 111789111 &&abc&!
java
package Regex;
public class RegexDemo8 {
public static void main(String[] args) {
//需求1:判断一个字符串的开始和结束字符是否一致?只考虑一个字符。
// \\组号,表示吧第X组的内容拿出来,再用一次
String regex1 = "(.).+\\1";
System.out.println("a123a".matches(regex1));
System.out.println("b456b".matches(regex1));
System.out.println("17891".matches(regex1));
System.out.println("&abc&".matches(regex1));
//需求2:判断一个字符串的开始部分和结束部分的多个字符是否一致。
//abc123abc b456b 123789123 &!@abc&!@
String regex2 = "(.+).+\\1";
System.out.println("abc123abc".matches(regex2));
System.out.println("b456b".matches(regex2));
System.out.println("123789123".matches(regex2));
System.out.println("&!@abc&!@".matches(regex2));
//需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致。
//*表示0次或多次
//(.)把首字母看作一组,\\2表示把首字母拿出来再次使用
// \\2*表示把第一组反复出现0次或多次
//aaa123aaa bbb456bbb 111789111 &&abc&!
String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex3));
System.out.println("bbb456bbb".matches(regex3));
System.out.println("111789111".matches(regex3));
System.out.println("&&abc&!".matches(regex3));
后续还要使用本组的内容,可以使用这两个符号:
java
正则内部使用:\\组号
正则外部使用:$组号
利用分组,我们可以把重复的冗余字段替换为单个字段。
java
package Regex;
public class RegexDemo9 {
public static void main(String[] args) {
//将字符串:我要学学编编编编程程程程程程
//替换为:我要学编程
String str = "我要学学编编编编程程程程程程";
//把重复的内容替换为单个的
//学学 学 编编编编 编 程程程程程程 程
//(.) 表示把重复内容的第一个字符看作一组
// \\1+ 表示第一个字符再次出现,1次或多次
// $1 表示把正则表达式中第一组的内容再拿出来用一次
//由于是在正则表达式外部,所以要用$符号
String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result);
}
}
10.2 非捕获分组
有的时候,我们在分组之后不再需要使用本组数据,仅仅是把数据括起来,这时候可以用这些符号。
标注非捕获分组的时候,小括号不占用组号。
java
package Regex;
public class RegexDemo10 {
public static void main(String[] args) {
//身份证号码简易正则表达式
//如果只是仅仅想把这组数据括起来,而不是想要继续使用括号里面的数据
//可以用?:标注小括号
//特点:不占用组号
String regex = "[1-9]\\d{16}(?:\\d|X|x)\\1"; // 报错,不知道第一组是谁
//(?=) (?!) 也是非捕获分组,但是一般情况使用(?:)
System.out.println("41080119930228547x".matches(regex));
}
}