本文内容是根据毕晓东老师的视频教程总结而得。
1.特点、概述
符合一定规则的表达式。作用:用于专门操作字符串。
String类中有各种操作字符串的各种方法为什么还要用正则表达式?String类中的方法比较复杂,而正则表达式使用起来会相对简单。
需求:对QQ号码进行校验,要求5~15位,0不能开头,只能是数字。
按照字符串方法进行校验:
package com.vnb.javabase.base.regex;
/**
* Description:以字符串形式对QQ号码进行校验,要求5~15位,0不能开头,只能是数字。
* @author
* @date 2018年10月23日
*/
public class RegexDemo {
public static void main(String[] args) {
checkQQ("343445643jygf8");
}
public static void checkQQ(String qq){
//判断长度是否在5~15位
if(qq.length()>=5 && qq.length()<=15){
//判断qq号是否以)开头
if(!qq.startsWith("0")){
//判断qq号是否为数字
boolean flag = true;//一旦发现某个位置不为数字则返回qq号非法
char[] arr = qq.toCharArray();
for (int i = 0; i < arr.length; i++) {
if(!(arr[i]>='0' && arr[i]<='9')){
flag = false;
break;
}
}
if(flag){
System.out.println("QQ:"+qq);
}else{
System.out.println(qq+".......QQ号非法");
}
}else{
System.out.println("qq号不能以0开头");
}
}else{
System.out.println("qq号长度不在5~15位之间");
}
}
}
前面的方式麻烦,可以使用Long进行转化,如果不是数字则会抛出异常出现非法字符(异常捕捉处理方式):
public static void checkQQ_2(String qq){
//判断长度是否在5~15位
if(qq.length()>=5 && qq.length()<=15){
//判断qq号是否以)开头
if(!qq.startsWith("0")){
//判断qq号是否为数字
try{
long qqLong = Long.parseLong(qq);
}catch(NumberFormatException e){
System.out.println(qq+".......QQ号非法");
}
}else{
System.out.println("qq号不能以0开头");
}
}else{
System.out.println("qq号长度不在5~15位之间");
}
}
结果:343ert4.......QQ号非法
这种方式是使用了String类中的方法进行组合完成了需求,但是代码过于复杂,以下使用正则表达式进行校验,通过字符串方法matchs()方法实现:
public static void checkQQ_3(String qq){
String regex = "[1-9][0-9]{4,14}";//第一位1到9;第二位只要是数字即可且第二位出现次数为4到14此
boolean flag = qq.matches(regex);
if(flag){
System.out.println(qq+"......ok");
}else{
System.out.println(qq+"......不合法");
}
}
结果:343445643jygf8......不合法
特点:用一些自定义的符号来表示一些代码操作,这样就简化书写。所以学习正则表达式就是在学习一些特殊符号的使用。
2.匹配
具体操作的功能:
匹配:String matches()方法,匹配整个字符串,有一个不匹配则结束
[abc]:[ ]判断一个字符串中某一个字符位上出现的字符,要么是a,要么是b,要么是c
[^abc]:除了abc三个字符以外的任意一个字符
package com.vnb.javabase.base.regex;
public class RegexDemo2 {
public static void main(String[] args) {
demo();
}
public static void demo(){
String str = "ab";
String reg = "[abc]";
boolean b = str.matches(reg);
System.out.println(b);
}
}
结果:false
String str = "b";
结果:true
. :表示该字符不做任何限制,任何字符都行
\xOB是男的符号,\f换页符,\w可用于邮箱(字母数字或者_)
X代表某个规则,如\d?表示数字出现一次或没有数字
练习:匹配手机号
public static void checkTel(String tel){
//手机号段只有13XXX 15XXXX 18XXXX
String regex = "1[358]\\d{9}";
boolean flag = tel.matches(regex);
if(flag){
System.out.println(tel+"......ok");
}else{
System.out.println(tel+"......不合法");
}
}
结果:13287879090......ok
16287879090......不合法
3.切割
3.1String类的split()方法:String[] split(String regex)
public static void splitDemo(){
/*String str = "23r,asdf,23";
String reg = ",";*/
String str = "23r asdf 23";
String reg = " +";
String[] arr = str.split(reg);
for(String s : arr){
System.out.println("s:"+s);
}
}
结果:在由多个空格的情况下,一般的切割无法匹配所有的空格,所以使用“ +”即可匹配一个或多个空格。
s:23r
s:asdf
s:23
3.2正则表达式中的"."
注意:"."是正则表达式的一个特殊符号表示任意字符,直接用"."切割是不行的
public static void splitDemo(){
/*String str = "23r,asdf,23";
String reg = ",";*/
/*String str = "23r asdf 23";
String reg = " +";*/
//注意:"."是正则表达式的一个特殊符号表示任意字符,直接用"."切割是不行的
String str = "23r.asdf.23";
String reg = ".";
String[] arr = str.split(reg);
for(String s : arr){
System.out.println("s:"+s);
}
}
结果:没有打印任何结果
一定要使用“.”进行切割,要转义,\.表示正则表达式的”.”,要转义”.”使用\\.
String reg = "\\.";
结果:
s:23r
s:asdf
s:23
3.3出现叠词进行切割
利用正则表达式的组:当想要对一个规则的结果进行重用时,可以将这个规则进行封装成组,组里面的结果就可以再次被使用。即(),通过\进行往回引用即(.)\1表示第一位的内容在第二位出现(.)\\1
按照叠词进行切割:叠词即后一个和前一个一致,前一个是任意字符,后一个和前一个字符相同。即"."(第一位的字符位“.”任意字符,第二位字符位是第一位的复用)。注意,没有写(),叫做默认第0组,所以不写()没有意义。
public static void splitDemo1(){
String str = "asdfzzgwerdda4rfdseeasgresf";
String reg = "(.)\\1";
String[] arr = str.split(reg);
for(String s : arr){
System.out.println("s:"+s);
}
}
结果:
s:asdf
s:gwer
s:a4rfds
s:asgresf
(.)\\1如果第一位是z,第二位也是z才匹配
如果想匹配一次或多次叠字(.)\\1+
为了可以让规则的结果被重用,可以将规则封装成一个组,用()实现,组的出现都有编号。从1开始,想要使用已有的组可以通过\n的形式来获取(n就是组的编号)
多个组:书写方便但是阅读性差
正则表达式弊端:符号定义越多,正则越长,阅读性越差
4.替换
String replaceAll(String regex,String replacement):根据正则进行替换
public static void main(String[] args) {
//要求:将字符串中的数字\d替换成#,连续超过5个数字就替换成#
String str = "asdfas1382398575784y12345678asdff";
//将叠词替换成#
String str1 = "erf3ee23rsfddddsdfsdf";
//将字符串中所有的叠词替换成#
//replaceAllDemo(str,"(.)\\1+","#");
replaceAllDemo(str,"\\d{5,}","#");
replaceAllDemo(str1,"(.)\\1+","&");
//将重叠的字母替换成单个字母:取完叠词后将新的字符设置成和组里面的字符一样(通过$1获取组里面的字符)
replaceAllDemo(str1,"(.)\\1+","$1");//$1表示获取前一个规则的第一个组,然后使用第一个符合规则的字符进行替换
}
public static void replaceAllDemo(String str,String reg,String newStr){
str = str.replaceAll(reg, newStr);
System.out.println("str:"+str);
}
结果:
str:asdfas#y#asdff
str:erf3&23rsf&sdfsdf
str:erf3e23rsfdsdfsdf
通过多个组的形式替换某一部分:
5.获取
将字符串中符合指定的规则的子串取出来
操作步骤:
- 将正则表达式封装成对象(Pattern、Matcher)
- 让正则表达式对象和要操作的字符串相关联(compile() find())
- 关联后,获取正则匹配引擎(group())
- 通过引擎对符合规则的子串进行操作,比如取出
Pattern类:正则表达式的编译表示形式,即描述封装的正则表达式(将正则表达式封装成对象),封装成对象了并没有对字符串进行操作,要对字符串进行操作,必须使用Matcher匹配器对象操作已有字符串
没有构造函数,只有静态本类对象如,
- static Pattern compile(String regex):eg,Pattern p = Pattern.compile(regex)
- 让正则对象和要作用的字符串相关联matcher(CharSequence cs),char值得可读序列,String是其子类对象,返回此模式的新匹配器通过解释Pattern对Charcter sequence执行匹配操作的引擎
- 获取匹配器对象,有了匹配器后,就有了多种方式操作字符串封装到了匹配器里
- matches():其实String类的matches方法用的就是封装后的Pattern中的matches()方法,用起来简单,但是功能单一。eg,Matcher m = p.matcher(str)
- find():尝试查找与模式匹配的输入序列的下一个子序列
- group():用于获取匹配后的结果,需要先执行find()方法才能执行group方法否则会抛出异常No match found
package com.vnb.javabase.base.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo3 {
public static void main(String[] args) {
getDemo();
}
public static void getDemo(){
//将连续三个字母的单词取出来(三个三个的取,取出来的是字母就获取,不是就不获取)
String str = "ming tian jiu yao fang jia le,da jia.";
String reg = "\\b[a-z]{3}\\b";
//将规则封装成对象
Pattern p = Pattern.compile(reg);
//让正则对象和要作用的字符串相关联matcher(CharSequence cs),char值得可读序列,String是其子类对象,返回此模式的新匹配器通过解释Pattern对Charcter sequence执行匹配操作的引擎
//获取匹配器对象,有了匹配器后,就有了多种方式操作字符串封装到了匹配器里
Matcher m = p.matcher(str);
//将规则作用到字符串上,并进行符合规则的子串查找
boolean b = m.find();
//System.out.println(b);
System.out.println(str);
while(m.find()){
//发现取出来的并不是英文单词,所以可以使用英文边界匹配器(\b单词边界)
System.out.println(m.group());
System.out.println(m.start()+"........."+m.end());
}
}
}
结果:
ming tian jiu yao fang jia le,da jia.
yao
14.........17
jia
23.........26
jia
33.........36
end()返回最后匹配的偏移量
start()
包头不包尾找取出来的数组角标。
先去进行匹配再进行查找,发现匹配返回false,获取到的结果,发现没有获取到:
matches作用于整个字符串,规则是连续4个字母,当查找到ming时,发现字符串还有东西,所以matches就不匹配了,但是此时匹配器已经走到tian的t了,前面不匹配了,所以索引位改变了,再使用m.find()时,就会从t开始进行匹配,即同一个匹配器使用的是同一个指针:
package com.vnb.javabase.base.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo3 {
public static void main(String[] args) {
getDemo();
}
public static void getDemo(){
//将连续三个字母的单词取出来(三个三个的取,取出来的是字母就获取,不是就不获取)
String str = "ming tian jiu yao fang jia le,da jia.";
String reg = "\\b[a-z]{4}\\b";
//将规则封装成对象
Pattern p = Pattern.compile(reg);
//让正则对象和要作用的字符串相关联matcher(CharSequence cs),char值得可读序列,String是其子类对象,返回此模式的新匹配器通过解释Pattern对Charcter sequence执行匹配操作的引擎
//获取匹配器对象,有了匹配器后,就有了多种方式操作字符串封装到了匹配器里
Matcher m = p.matcher(str);
//将规则作用到字符串上,并进行符合规则的子串查找
boolean b = m.find();
//System.out.println(b);
System.out.println(str);
System.out.println("matches:"+m.matches());
while(m.find()){
//发现取出来的并不是英文单词,所以可以使用英文边界匹配器(\b单词边界)
System.out.println(m.group());
System.out.println(m.start()+"........."+m.end());
}
}
}
结果:
ming tian jiu yao fang jia le,da jia.
matches:false
tian
5.........9
fang
18.........22
6.练习1
需求:将下列字符串转成:我要学编程
到底使用四种功能的哪一个?或者哪几个呢?
字符串:我我....我我..我要…我要…要要…学学学…学学….编编…编程
思路:
- 如果只想知道该字符串是否对或错,使用匹配
- 想要将已有的字符串变成李另一个字符串,使用替换
- 想要按照自定义的方式将字符串变成多个字符串,使用切割。获取规则以外的子串
- 想要拿到(获取)符合需求的字符串子串,使用获取。获取符合规则的子串。
此处是将已有字符串变成另一个字符串,使用替换功能。
步骤:
先将“.”去掉
再将多个重复的内容变成单个内容
package com.vnb.javabase.base.regex;
public class RegexTest {
public static void main(String[] args) {
String str = "我我....我我..我要...要要....学学学....学学....编编....编程";
reverseStr(str);
}
public static void reverseStr(String str){
//先将.去掉
str = str.replaceAll("\\.+", "");
System.out.println("str:"+str);
//再去除重复,如果出现重复使用第一组替换
str = str.replaceAll("(.)\\1+", "$1");
System.out.println("str:"+str);
}
}
结果:
str:我我我我我要要要学学学学学编编编程
str:我要学编程
7.练习2
需求:将IP地址进行按顺序排序
192.168.1.254 102.49.23.13 10.10.10.10 2.2.2.2 8.109.90.30
取出空格,再排序
思路:
- 因为如果位数不同,有的1位有的2位有的3位就会不好比较,所以使用补0方式将1和2位的补全为3位再比较
- 每一段的位数都不一样,那么要补多少0?可以每一段都补2个0保证至少有3位
- 将每一段只保留3位,这样所有的IP地址都是每一段2位
package com.vnb.javabase.base.regex;
import java.util.TreeSet;
public class RegexTest2 {
//将IP地址进行按顺序排序192.168.1.254 102.49.23.13 10.10.10.10 2.2.2.2 8.109.90.30
public static void main(String[] args) {
sortIp();
}
public static void sortIp(){
String ip = "192.168.1.254 102.49.23.13 10.10.10.10 2.2.2.2 8.109.90.30";
//因为每个IP地址的位数不同,所以强行将每个IP都补2位0,这样就可以保证每个IP段上至少有3位
ip = ip.replaceAll("(\\d+)", "00$1");//将所有连续数字(一个或多个)头部加上2个00
//ip:00192.00168.001.00254 00102.0049.0023.0013 0010.0010.0010.0010 002.002.002.002 008.00109.0090.0030
System.out.println("ip:"+ip);
//因为原本就有大于1位的IP段在加上两个0后,位数超过了3位,所以要将超过3位的0去掉
ip = ip.replaceAll("0*(\\d{3})", "$1");//先匹配前面0个或多个0,再匹配数字,如果数字已经有3位就只取数字
System.out.println("ip:"+ip);//ip:192.168.001.254 102.049.023.013 010.010.010.010 002.002.002.002 008.109.090.030
//通过" "空格切割字符串成Ip数组
String[] arr = ip.split(" +");//空格可能会有1个或多个
//遍历数组
System.out.println("通过空格切割后的字符串数组:");
//建立TreeSet集合,通过TreeSet集合进行排序
TreeSet<String> ipSet = new TreeSet<String>();
for(String s : arr){
System.out.println(s);
//将字符串添加进集合中,添加时去掉每个ip端中前面的0
ipSet.add(s.replaceAll("0*(\\d+)", "$1"));
}
//遍历TreeSet集合后,ip已经排好序
System.out.println("遍历且排序好的Ip集合:");
for(String s : ipSet){
System.out.println(s);
}
}
}
结果:需要在遍历集合的时候在去除0才能正确比较,否则只会按照字符串顺序进行排序
ip:00192.00168.001.00254 00102.0049.0023.0013 0010.0010.0010.0010 002.002.002.002 008.00109.0090.0030
ip:192.168.001.254 102.049.023.013 010.010.010.010 002.002.002.002 008.109.090.030
通过空格切割后的字符串数组:
192.168.001.254
102.049.023.013
010.010.010.010
002.002.002.002
008.109.090.030
遍历且排序好的Ip集合:
2.2.2.2
8.109.90.30
10.10.10.10
102.49.23.13
192.168.1.254
8.练习3
需求:对邮件地址进行校验
精确的匹配:
- @前6到12位,保证尽量不重复;字母(大小写)、数字、下划线;[a-zA-Z_0-9]{6,12}
- @固定
- @后面字母或数字一次或多次
- .固定
- .后字母(大小写)一次或多次
- 但是有可能.com和.cn有可能都有,有可能只有其中一个所以使用组(最多3次重复):(\\.[a-zA-Z]+)+
public static void checkMail(){
String mail = "adssdff@sina.com.cn";
//String regex = "[a-zA-Z_0-9]{6,12}@[a-zA-Z0-9]{2,12}(\\.[a-zA-Z]+)+";
String regex = "\\w{6,12}@[a-zA-Z0-9]{2,12}(\\.[a-zA-Z]+)+";
boolean b = mail.matches(regex);
System.out.println("邮箱输入是否正确:"+b);
}
结果:
邮箱输入是否正确:true
相对不太精确的匹配:
\w代表:[a-zA-Z_0-9]
所以使用\\w+@\\w+(\\.\\w+)+也可以粗略匹配,但是这样1@1.1也能过
只要找到邮件地址的特征,@存在即可
9.网页爬虫(蜘蛛)
通过爬虫获取到网页上所有的邮箱等
数据源:txt文件,源文件里有很多邮件地址
需求:将数据源里的邮箱地址爬取出来
思路:
获取指定文档中的邮件地址。使用获取功能,Pattern Matcher
mail.txt
asdfasdfanhasv weasdf@sffj.com asdfasdfanhasv
afasfs 873rasdfwr2435aSfsafbdvc lmf@qq.com.cn sdfasf
fsafjiaosdjfasdf sdwes238924859jidf@dsifh.com afasdf
asdfasdfh9jsf43r93@oasifjodif.com asfasdf
89247weiuafhs@asfjaso.comads.cn
asdfa98weuasf asf938ufies asf lisi@160.com asdfasf
package com.vnb.javabase.base.regex;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest3 {
public static void main(String[] args) {
touchMail1();
}
public static void touchMail1(){
//字符流 文本 需要缓冲
try {
//通过字符流独缺到
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("mail.txt")));
String line = null;
//System.out.println("txt文件所有内容:");
try {
while((line=br.readLine())!=null){
//System.out.println(line);
//获取到文件所有内容后,通过正则获取功能进行处理
String regex = "\\w{6,12}@[a-zA-Z0-9]{2,12}(\\.[a-zA-Z]+)+";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(line);
while(m.find()){
System.out.println(m.group());
}
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
结果:
weasdf@sffj.com
38924859jidf@dsifh.com
dfh9jsf43r93@oasifjodif.com
9247weiuafhs@asfjaso.comads.cn
如果爬取网页上的邮箱地址:
自己造一个网页出来,有邮箱相关地址,再从该网页爬取邮箱地址
url.openConnection()获取到网页连接器,在通过其conn.getInputStream()获得数据
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset="utf-8">
<title>网页</title>
</head>
<body>
asdfasdfanhasv weasdf@sffj.com asdfasdfanhasv
afasfs 873rasdfwr2435aSfsafbdvc lmf@qq.com.cn sdfasf
fsafjiaosdjfasdf sdwes238924859jidf@dsifh.com afasdf
asdfasdfh9jsf43r93@oasifjodif.com asfasdf
89247weiuafhs@asfjaso.comads.cn
asdfa98weuasf asf938ufies asf lisi@160.com asdfasf
</body>
</html>
public static void touchMail2(){
try {
URL url = new URL("http://localhost:8080/SpringmvcQuartz/print");
try {
URLConnection conn = url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = null;
//System.out.println("txt文件所有内容:");
try {
while((line=br.readLine())!=null){
//System.out.println(line);
//获取到文件所有内容后,通过正则获取功能进行处理
String regex = "\\w{6,12}@[a-zA-Z0-9]{2,12}(\\.[a-zA-Z]+)+";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(line);
while(m.find()){
System.out.println(m.group());
}
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
结果:
touchMail2
weasdf@sffj.com
38924859jidf@dsifh.com
dfh9jsf43r93@oasifjodif.com
9247weiuafhs@asfjaso.comads.cn