Skip to content

Java

正则表达式

正则表达式可以校验字符串是否满足一定的规则, 并用来校验数据的合法性

  1. 校验字符产是否满足规则
  2. 在一段文本总查找满足要求的内容
java
public class Test4 {
    public static void main(String[] args) {
        //要求: 个数在6-20之间, 第一位不能为0
        String qq = "1234567890";
        boolean flag = checkQQ(qq);
        System.out.println(flag);
    }

    //校验数据
    public static boolean checkQQ(String qq) {
        int qqLen = qq.length();
        if (qqLen <= 6 || qqLen >= 20) return false;
        //第一位为0
        if (qq.startsWith("0")) return false;

        return true;
    }
}

如果校验过于复杂就可使用正则表达式


  1. 使用正则表达式校验数据
java
public class Test4 {
    public static void main(String[] args) {
        //要求: 个数在6-20之间, 第一位不能为0
        String qq = "1234567890";
        //matches的参数式正则表达式规则
        boolean flag = qq.matches("[1-9]\\d{5,20}");
        System.out.println(flag);
    }

}

字符类

字符类(只匹配一个字符)描述
[abc]只能是a,b,c
[^abc]不能是a,b,c, 相当于取反
[a-zA-Z]a-z, A-Z
[a-d[m-p]]a到d或者m到p, 和上一个差不多
[a-z&&[def]]a-z和def的交集, 为d, e, f
[a-z&&[^bc]]a-z和非bc的交集, 为a, d-z 相当于:[ad-z]
[a-z&&[^m-p]]a-z和除了m-p的交集, 为a-l, q-z 相当于:[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]

转义字符

转义字符描述
\转义字符, 作用: 改变后面一个字符原本的意义
java
public class Test4 {
    public static void main(String[] args) {
        //在window下文件路径一般只有一个\, 而\在java中有特殊意义, 所以需要转义
        String path = "D:\\JAVA文件\\java_study\\puzzle-game\\image\\about.png";
    }
}

量词

量词(匹配前面的字符多次)描述
X*X, 出现0次或多次
X+X, 出现1次或多次
X?X, 出现0次或1次
X{n}X, 出现n次
X{n,}X, 出现n次或多次
X{n,m}X, 出现n到m次

逻辑运算符

逻辑运算符描述
|
()括号将规则组合
&&逻辑与
[abc]a或b或c

忽略大小写

  • (?i) -- 忽略大小写
java
//忽略abc的大小写
String regex4 = "(?i)abc";
System.out.println("abc".matches(regex4));// true
System.out.println("Abc".matches(regex4));// true
System.out.println("aBC".matches(regex4));// true

//忽略bc的大小写
String regex5 = "a(?i)bc";
System.out.println("abc".matches(regex5));// true
System.out.println("Abc".matches(regex5));// false
System.out.println("aBC".matches(regex5));// true

//只忽略b的大小写, 就是用括号单独括起来就行
String regex6 = "a((?i)b)c";
System.out.println("abc".matches(regex6));// true
System.out.println("Abc".matches(regex6));// false
System.out.println("aBC".matches(regex6));// false

综合练习

java
public class Demo1 {
    public static void main(String[] args) {
        /*
        * ​	请编写正则表达式验证用户输入的手机号码是否满足要求。

​	请编写正则表达式验证用户输入的邮箱号是否满足要求。

​	请编写正则表达式验证用户输入的电话号码是否满足要求。

​	验证手机号码 13112345678 13712345667 13945679027 139456790271

​	验证座机电话号码 020-2324242 02122442 027-42424 0712-3242434

​	验证邮箱号码 3232323@qq.com zhangsan@itcast.cnn dlei0009@163.com dlei0009@pci.com.cn
        * */

        /*心得: 编写正则不要以全局的方式看待, 而是局部的从左到右*/


        //第一部分 1 即第一个字符为1
        //第二部分 [3-9] 即第二个字符为3到9之间的数字
        //第三部分 \\d{9} 即匹配9个数字
        String regex1 = "1[3-9]\\d{9}";
        System.out.println("13112345678".matches(regex1));// true
        System.out.println("13712345667".matches(regex1));// true
        System.out.println("13945679027".matches(regex1));// true
        System.out.println("139456790271".matches(regex1));// false
        System.out.println("---------------------------------");
        //1. 区号 0\\d{2,3}
        //1.1 首字符为0; 剩余的数字是随机的, 可能是2位, 可能是3位
        //2. -         -?
        //2.1  '-'只存在一次或0次  ?表示0次或者1次
        //3. 座机号 第一位不能是0, 其余可以是任何数字, 号码总长度: 5-10位 [1-9]\d{4,9}
        String regex2 = "0\\d{2,3}-?[1-9]\\d{4,9}";
        System.out.println("020-2324242".matches(regex2));
        System.out.println("02122442".matches(regex2));
        System.out.println("027-42424".matches(regex2));
        System.out.println("0712-3242434".matches(regex2));
        System.out.println("---------------------------------");

        String regex3 = "\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}";
        System.out.println("3232323@qq.com".matches(regex3));
        System.out.println("zhangsan@itcast.cnn".matches(regex3));
        System.out.println("dlei0009@163.com".matches(regex3));
        System.out.println("dlei0009@pci.com.cn".matches(regex3));


        //严格验证身份证信息 420704 20031028 153 X
        //1. 市区 省份 派出所 第一位不可以为0 2-5为任意数字 [1-9]\\d{5}
        //2. 出身年月日 20 03 10 28                       
        //2.1 年的前半部分 有身份证的都 18 19 20年         (18|19|20)
        //2.2 年的后半部分 2位任意数字                      \\d{2}
        //3. 月份 1 ~ 12                                  (0[1-9]|1[0-2])
        //4. 日 1- 31  ==> 01 02 10 20 30 31                (0[1-9]|[12]\\d|3[01])
        //5. 153位任意3位数字                               \\d{3}
        //6. 数字|x | X 最后一位可以是数字或者大小写x           [0-9xX]
        String strictIdRule = "[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9xX]";
        System.out.println("42070420031028153X".matches(strictIdRule));
        System.out.println("02070420031028153X".matches(strictIdRule));
        System.out.println("42070420031028153x".matches(strictIdRule));
        System.out.println("42070417351028153X".matches(strictIdRule));
    }
}

本地爬虫和网络爬虫

  1. 本地爬虫:从本地文件读取数据,例如:从本地文件读取HTML页面,解析HTML页面,提取数据。
  2. 网络爬虫:从互联网上获取数据,例如:从互联网上获取HTML页面,解析HTML页面,提取数据。
java
//本地爬虫
public class Demo2 {
    public static void main(String[] args) {
        String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
                "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
        //获取正则表达式对象
        Pattern p = Pattern.compile("Java\\d{0,2}");
        //文本匹配器对象
        Matcher m = p.matcher(str);
        //m.find()会拿着文本匹配器从头开始读取, 寻找有满足规则的子串
        //如果没有返回false
        //如果有, 返回true, 并在底层记录子串的起始索引和结束索引+1
        while (m.find()) {
            //m.group()会根据find方法记录的索引进行字符串截取
            //使用substring(start, end); 最终会把子串返回
            String group = m.group();
            System.out.println(group);
        }
    }
}

最终输出

Java Java8 Java11 Java17 Java17


网络爬虫

java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Pattern;
public class Demo3 {
    public static void main(String[] args) throws IOException {
        //网络爬取
        URL url = new URL("https://cn.vuejs.org/");
        URLConnection con = url.openConnection();
        BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String regex = "[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9xX]";
        String line;

        Pattern p = Pattern.compile(regex);
        while ((line = br.readLine()) != null) {
//            Matcher m = p.matcher(line);
//            System.out.println(m.group());
            System.out.println(line);
        }
    }
}

条件爬取

  • ?= -- 配合占位符,表示后面必须匹配才满足条件,例如:(?=8|11|17)
  • ?: -- ((?i)java)(8|11|17)就相当于((?i)java)(?:8|11|17)
  • ?! -- 表示后面不能匹配才行,例如:(?!8|11|17)
  1. 需求1: 爬取版本号为8,11.17的Java文本,但是只要Java,不显示版本号。
  2. 需求2: 爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17
  3. 需求3: 爬取除了版本号为8,11.17的Java文本,
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Demo5 {
    public static void main(String[] args) {

        String s = "java自从95年问世以来,经历了很多版本,目前企业中用的最多的是JAva8和JavA11," +
                "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久JAva17也会逐渐登上历史舞台";
        // ?=8|11|17中的 ? 相当于是占位符
        String regex = "((?i)java)(?=8|11|17)";

        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(s);

        while (m.find()) {
            //最终取得过程中不会显示Java后面的数字, 但Java后面带数字是条件, 例如第一个Java后面没有数字就不符合条件
            System.out.println(m.group());
        }
    }

    /*
        最终输出:
        JAva
        JavA
        Java
        JAva
    */
}
java
String regex2 = "((?i)java)(8|11|17)";
String regex3 = "(?i)java(?:8|11|17)";//与regex2功能相同
java
//匹配java后面没有8|11|17的位置
String regex4 = "((?i)java)(?!8|11|17)";

贪婪爬取与非贪婪爬取

  • 贪婪爬取: 在爬取数据的时候尽可能的多获取数据
  • 非贪婪爬取: 在爬取数据的时候尽可能的少获取数据
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Demo6 {
    public static void main(String[] args) {
        String s = "Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa" +
                "经历了很多版木,目前企业中用的最多的是]ava8和]ava11,因为这两个是长期支持版木。" +
                "下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
        //贪婪爬取, 尽可能的多获取数据
        Pattern p = Pattern.compile("ab+");
        //非贪婪爬取, 在之前的基础上添加一个问号
        // Pattern p = Pattern.compile("ab+?");
        Matcher m = p.matcher(s);
        while(m.find()) {
            System.out.println(m.group());
            //abbbbbbbbbbbb
        }
    }
}

正则表达式在字符串方法中的使用

方法名描述
boolean matches(String regex)如果此字符串与给定的正则表达式匹配,则返回true
String replaceAll(String regex, String replacement)使用给定的替换字符串替换所有匹配给定正则表达式的字符串
String[] split(String regex)根据匹配给定的正则表达式来拆分此字符串

方法使用

java
public class Demo6 {
    public static void main(String[] args) {
        String s2 = "小小汪abc123大大王ab123c励志汪";
        //匹配字至少一次
        String newStr = s2.replaceAll("[0-9a-zA-z]+", "vs");
        System.out.println(newStr);// 小小汪vs大大王vs励志汪

        //根据匹配给定的正则表达式来拆分此字符串
        String[] split = newStr.split("\\w+");
        for (int i = 0; i < split.length; i++) {
            System.out.println(split[i]);
        }
        /*
            小小汪
            大大王
            励志汪
        */
    }
}

分组

分组就是使用括号()括起来的部分

捕获分组

  • 语法格式 ==> \\元组位置
  • 作用: 把一组的数据捕获出来, 再用一次. 注: 是数据, 不是正则表达式的规则再用一次
  • 判断捕获分组的规则
    1. 1开始, 连续不间断
    2. 以左括号为基准, 最左边的是第一组, 其次是第二组, 以此类推
java
//需求1:判断一个字符串的开始字符和结束字符是否一致?只考虑一个字符
//举例: a123a b456b 17891 &abc& a123b(false)
//(.)为第一i个元组, 而之后的\\1就是把(.)匹配的数据拿过来再用一次
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));
System.out.println("a123b".matches(regex1));
//需求2:判断一个字符串的开始部分和结束部分是否一致?可以有多个字符
//举例: abc123abc b456b 123789123 &!@abc&!@ abc123abd(false)
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));
System.out.println("abc123abd".matches(regex2));
System.out.println("--------------------------------");
//需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致
//举例: aaa123aaa bbb456bbb 111789111 &&abc&&
String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex2));
System.out.println("bbb456bbb".matches(regex3));
System.out.println("111789111".matches(regex3));
System.out.println("&&abc&&".matches(regex3));
System.out.println("111789112".matches(regex3));

解析((.)\\2*).+\\1

根据捕获分组的规则, 以左括号为基准, 最左边的是第一组, 其次是第二组, 以此类推.

  1. 这里的\\1指的是((.)\\2*)这一个整体
  2. \\2指的是(.)

使用分组的情况

如果想要后续使用本组的数据, 需由两个注意点

  1. 正则内部使用: \\组号
  2. 正则外部使用: $组号
java
String str = "我要学学编编编编程程程程程程";

//需求:把重复的内容 替换为 单个的
//学学                学
//编编编编            编
//程程程程程程        程
//result: 我要学编程
// (.)表示任意字符, \\1表示第一个元组匹配至少一次 例: 当.匹配到"学"的时候, 那么\\1也表示"学", 最后会替换为$1也就是第一个元组,
//而此时一个元组的数据就是学, 最终就把 "学学" ==> "学"
String result = str.replaceAll("(.)\\1+", "$1");//使用$外部引用元组的数据
System.out.println(result);

非捕获分组

分组之后不需要再用本组数据, 仅仅把数据括起来

符号含义例子
(?:正则)获取所有Java(?:8|11|17)
(?=正则)获取前面的部分Java(?:8|11|17)
(?!正则)获取不是指定内容的前面Java(?:8|11|17)
java
//需求:判断一个字符串是否是合法的身份证号
String regex1 ="[1-9]\\d{16}(\\d|x|X)";
System.out.println("42070420031028153X".matches(regex1));

// [1-9]\\d{16}(\\d|x|X)中的规则, (\\d|x|X)如果只需要使用一次就可以在括号内添加?:
String regex2 ="[1-9]\\d{16}(?:\\d|x|X)";
System.out.println("42070420031028153X".matches(regex2));// true
//报错: 因为使用?:括起来的元组不可以算作捕获元组
String regex1 ="[1-9]\\d{16}(?:\\d|x|X)\\1";
System.out.println("42070420031028153X".matches(regex1));

注意

()里面使用了?:, 那么就不可以捕获此元组的数据


Released under the MIT License.