【JAVA入门】Day20 - 正则表达式

【JAVA入门】Day20 - 正则表达式



        正则表达式可以校验字符串是否满足一定的规则,并用来校验数据格式的合法性。利用正则表达式校验字符串,可以省去大量代码。

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 一个 [ ] 匹配一个字符

        需要注意的是,一个[ ]内部只能匹配一个字符:

System.out.println("ab".matches("[abc]"));			//false
System.out.println("ab".matches("[abc][abc]"));		//true

        一个中括号只能管得了一个字符,它们是逐个字符匹配的。若想匹配多个字符,就要写多个中括号。

System.out.println("aa".matches("[a-zA-Z]"));				//false
System.out.println("aa".matches("[a-zA-Z][a-zA-Z]"));		//true

1.2 表示“或者”的表达式可以再用一个 [ ] 括起来

        为了增强可读性,二者写法等价,表示 “或者”:

System.out.println("a".matches("[a-dm-p]"));			//true
System.out.println("a".matches("[a-d[m-p]]"));			//true

1.3 &&表示“而且”

        如果想求两个范围的交集,一定要写两个 & 。否则会被识别为单个’&'符号。

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。

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]

二、预定义字符(只能匹配一个字符)

.				任何字符
\d				一个数字:[0-9]
\D				一个非数字:[^0-9]
\s				一个空白字符:[\t\n\x0B\f\r]
\S				一个非空白字符[^\s]
\w				英文、数字、下划线[a-zA-Z_0-9]
\W				一个非单词字符[^\w]

2.1 转义字符

\ 				是一个转义字符,用来改变后面跟的那个字符原本的含义
System.out.println("\"");			//"
System.out.println("\\");			//\

2.2 . 表示任意字符

System.out.println("你".matches("."));		//true
System.out.println("你a".matches(".."));	//true

2.3 \d 表示任意一位数字

        注意:\d 才是表示任意一位数字,在字符串中需要先用 双杠 把 \ 转换为其本来的意思。

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]

System.out.println("_".matches("\\w"));				//true
System.out.println("你".matches("\\w"));			//false
System.out.println("2".matches("\\w"));				//true

三、数量词

        如果想要让一个正则表达式出现多次,可以用数量词修饰。
在这里插入图片描述

//必须是数字、字母、下划线,至少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】需求:利用正则表达式,验证用户输入的:手机号、邮箱号、电话号码,是否满足要求。

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小时的正则表达式。

//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】写用户名和身份证的正则表达式。

 /*
        验证用户名是否满足要求:大小写字母,数字,下划线一共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 格式的文本子串。

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匹配出想要的内容即可。

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();
    }
}

【练习】利用正则表达式爬取文本中的座机号码、邮箱、手机号、热线号码。

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());
        }
    }
}

七、使用正则表达式进行带条件的爬取

        使用正则表达式爬取数据时,我们有时需要一些限制条件。

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自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaa
        经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来
        不久Java17也会逐渐登上历史舞台
         */

        需求1:按照ab+的方式爬取ab,b尽可能多获取。
        需求2:按照ab+的方式爬取ab,b尽可能少获取。
        解释:ab+表示a出现1次,b出现1次或多次,b可以只出现1次,也可以全部出现。
        贪婪爬取:abbbbbbbbbbbb。
        非贪婪爬取:ab。
        在 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)按照正则表达式的规则切割字符串

【练习】使用以上方法操作字符串。

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]);
        }
    }
}

十、分组

        正则表达式中的分组刚才已经介绍过,其实就是小括号。
        由于有“或”、“且”等逻辑符号的存在,分组在正则表达式中的编写是必须存在的,否则去掉括号的话下面的正则表达式就会改变原意(或的范围变化)。

String regex = "[1-9]\\d{16}(\\d|X|x)";

        分组的另一个用途则是可以简化代码。

//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:以左括号为基准,最左边的左括号是第一组,其次为第二组,以此类推。
(\\d+)(\\d+)(\\d+)
//第一组 第二组 第三组

(\\d+(\\d+))(\\d+)
//第一组 第二组 第三组

        分组编号的意义在于捕获。

10.1 捕获分组

        捕获分组就是把这一组的数据捕获出来,再用一次。看下面的例子。

【练习】需求1:判断一个字符串的开始和结束字符是否一致?只考虑一个字符。

a123a b456b 17891 &abc&

需求2:判断一个字符串的开始部分和结束部分的多个字符是否一致。

abc123abc b456b 123789123 &!@abc&!@

需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致。

aaa123aaa bbb456bbb 111789111 &&abc&!

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));

        后续还要使用本组的内容,可以使用这两个符号:

正则内部使用:\\组号
正则外部使用:$组号

        利用分组,我们可以把重复的冗余字段替换为单个字段。

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 非捕获分组

        有的时候,我们在分组之后不再需要使用本组数据,仅仅是把数据括起来,这时候可以用这些符号。
在这里插入图片描述
        标注非捕获分组的时候,小括号不占用组号。

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));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值