1、正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表通常被用来检索、替换那些符合某个模式(规则)的文本。
1.1、概念
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
1.2、正则表达式可以做什么
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
- 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”);
- 可以通过正则表达式,从字符串中获取我们想要的特定部分。(爬虫应用)
1.3、正则表达式的特点
- 灵活性、逻辑性和功能性非常的强;
- 可以迅速地用极简单的方式达到字符串的复杂控制。
- 对于刚接触的人来说,比较晦涩难懂。(缺点)
由于正则表达式主要应用对象是文本(字符串),因此它在各种文本编辑器场合都有应用,小到著名编辑器EditPlus,大到Microsoft Word、Visual Studio等大型编辑器,都可以使用正则表达式来处理文本内容。
其他相关概念
引擎
正则引擎主要可以分为两大类:一种是DFA,一种是NFA。这两种引擎都有了很久的历史(至今二十多年),当中也由这两种引擎产生了很多变体!于是POSIX的出台规避了不必要变体的继续产生。这样一来,主流的正则引擎又分为3类:
一、DFA,二、传统型NFA,三、POSIX NFA。
1.4、符号
正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义。【请查阅相关元字符表】
java.util.regex.Pattern
1.5、用途
(1)字符串匹配(字符匹配)
(2)字符串查找
(3)字符串替换
例如:
(1)手机号码是否符合规范等
(2)邮箱地址是否正确
(3)IP地址是否正确
1.6、相关类
java.lang.String
java.util.regex.Pattern
java.util.regex.Matcher
2、案例演示
(1)QQ校验
(2)手机号码验证
(3)邮箱地址校验 dsfd@fsd.fds
(4)获取由三个字符组成的单词
2.1、案例1
public class qq {
public static void main(String[] args) {
// String.matches(String regex);
String regex = "[1-9][0-9]{4,14}";//正则表达式
boolean b = "204227136".matches(regex);
System.out.println(204227136+" : "+b);
}
// 需求:定义一个功能对QQ号进行校验
// 要求:长度5-15,只能是数字,0不能开头
public static void checkQQ(String qq) {
int len = qq.length();
if (len >= 5 && len <= 15) {
if (!qq.startsWith("0")) {
try {
long l = Long.parseLong(qq);
System.out.println(l + " 正确!");
} catch (NumberFormatException e) {
System.out.println(qq + ":含有非法字符!");
}
} else {
System.out.println(qq + " : 不能以0开头");
}
}else{
System.out.println(qq+" 长度错误!");
}
}
}
正则表达式(常见的规则) | 可查看JDK API 文档 |
---|---|
字符类 | |
[abc] | a、b 或 c(简单类) (某一位上只能是a,b,c) |
[^abc] | 任何字符,除了 a、b 或 c(否定) |
[a-zA-Z] | a 到 z 或 A 到 Z,两头的字母包括在内(范围) |
[a-d[m-p]] | a 到 d 或 m 到 p:[a-dm-p](并集) |
[a-z&&[def]] | d、e 或 f(交集) |
[a-z&&[^bc]] | a 到 z,除了 b 和 c:[ad-z](减去) |
[a-z&&[^m-p]] | a 到 z,而非 m 到 p:[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] |
边界匹配器 | |
^ | 行的开头 |
$ | 行的结尾 |
\b | 单词边界 |
\B | 非单词边界 |
\A | 输入的开头 |
\G | 上一个匹配的结尾 |
\Z | 输入的结尾,仅用于最后的结束符(如果有的话) |
\z | 输入的结尾 |
Greedy 数量词 | |
X? | X,一次或一次也没有 (举例演示) |
X* | X,零次或多次 |
X+ | X,一次或多次 |
X{n} | X,恰好 n 次 |
X{n,} | X,至少 n 次 |
X{n,m} | X,至少 n 次,但是不超过 m 次 |
2.2、案例2
public class RegexDemo2 {
public static void main(String[] args) {
String str ="aooooooooooooooooooooooooooob";
//String reg = "ao?b";
//String reg = "ao{4}b";
//String reg = "ao{4,}b";
//String reg = "ao{4,6}b";
//String reg = "ao+b";
String reg = "ao*b";
boolean b = str.matches(reg);
System.out.println(b);
}
}
2.3、案例3
需求:手机号码验证(必讲)
正则表达式对字符串的常见操作:
1、匹配
其实使用的就是String类中的matches()方法
2、切割
其实使用的就是String类中的split()方法
3、替换
其实使用的就是String类中的replaceAll()方法
4、获取
/**
- 需求:手机号码验证
- 正则表达式对字符串的常见操作:
- 1、匹配
- 其实使用的就是String类中的matches()方法
- 2、切割
- 其实使用的就是String类中的split()方法
- 3、替换
- 其实使用的就是String类中的replaceAll()方法
- 4、获取
*/
public class RegexDemo3 {
public static void main(String[] args) {
// method1();
// method2();
method3();
// method4();
}
/*
* 演示匹配 匹配手机号码是否正确 (必讲) matches()方法
*/
public static void method1() {
String tel = "15804901111";
String regex = "1[358][0-9]{9}";
// String regex = "1[358]\\d{9}";
boolean b = tel.matches(regex);
System.out.println(tel + " : " + b);
}
/*
* 切割 split()方法 组:((A)(B(C))) 选讲
*/
public static void method2() {
String str1 = "zhangsan xiaoqiang zhaoliu";
// String str2 = "zhangsan.xiaoqiang.zhaoliu";
// String str3 = "zhangsanttttxiaoqiangmmmmmmzhaoliu";//组
String[] names = str1.split(" ");
// String [] names = str1.split(" +");
// String [] names = str2.split("\\.");
// String [] names = str3.split("(.)\\1+");//组
for (String name : names) {
System.out.println(name);
}
}
/*
* 替换 replaceAll()方法
*/
public static void method3() {
String str = "zhangsanttttxiaoqiangmmmmmmzhaoliu";
str = str.replaceAll("(.)\\1+", "$1");//
System.out.println(str);
String tel = "15856567788";
tel = tel.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
System.out.println(tel);
}
public static void method4(){
String str = "da jia hao,ming tian bu fang jia";
//String regex = "[a-z]{3}";
String regex = "\\b[a-z]{3}\\b";// 单词边界 \\b
//1、将正则规则进行对象的封装
Pattern p = Pattern.compile(regex);
//2、通过正则对象获取匹配器对象
Matcher m = p.matcher(str);
//3、使用Matcher对象的方法对字符串进行操作。
// 既然要获取三个字母组成的单词
// 查找find();
// m.find();
// System.err.println(m.group());//获取匹配的子序列
System.out.println(str);
while(m.find()){
System.out.println(m.group());//获取匹配的子序列
System.out.println(m.start()+" : "+m.end());
}
}
}
/*
* method4
* 业务:获取由三个字符组成的单词
* 见API
指定为字符串的正则表达式必须首先被编译为此类的实例。然后,可将得到的模式用于创建 Matcher 对象,依照正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式。
将正则规则进行对象的封装
Pattern p = Pattern.compile(“a*b”);
通过正则对象的matcher方法字符串相关联。获取要对字符串操作的匹配器对象Matcher。
Matcher m = p.matcher(“aaaaab”);
通过Matcher匹配器对象的方法对字符串进行操作。
boolean b = m.matches();
*/
注意:(.)\1+
"(.+)(.*?)\\1"表示什么意思呢?
.表示单个字符+表示一个或多个, .+表示一个或多个单字符。
()是什么意思呢?
举个例子
( a.)+ 表示一个或多个a.此处.表示字符
a(.+)表示a一个或多个.
区别在于第一个表示的a和.数量是相同的
第二行只有一个a
所以()的意思就是限定+修饰的字符
(.*?)表示任意多个单字符(包括没有) 这个?是什么意思呢?表示最小的匹配
\\1表示第一个括号中匹配到的字符
字符串a2a2a2中"(.+)(.*?)\\1" 改变后为a2a2 即$1匹配和\\1是相同的只能是a2 $2就匹配为中间的a2
若字符串为1111则$1和\\1匹配的就是11和11$2匹配到的就是空的
$1$2分别表示第一个()和第二个()匹配的值
2.4、案例4
需求1:治疗口吃:我我…我我…我要…要要要要…要要要要…学学学学…编编编编编…程程
需求2:对ip地址排序
需求3:对邮件地址校验
import java.util.Arrays;
import java.util.TreeSet;
/**
*
- 需求1:治疗口吃:我我…我我…我要…要要要要…要要要要…学学学学学…编编编编编…程程程程
- 需求2:对ip地址排序 需求
- 需求3:对邮件地址校验
*/
public class RegexTest {
/**
* @param args
*/
public static void main(String[] args) {
// test1();
test2();
// test3();
}
/**
* 1、治口吃
*/
public static void test1() {
String str = "我我...我我...我要...要要要要...要要要要...学学学学学...编编编编编....程程程程";
// 1.将字符串中.去掉。用替换
str = str.replaceAll("\\.+", "");
System.out.println(str);
// 2.替换叠词
str = str.replaceAll("(.)\\1+", "$1");
System.out.println(str);
}
/**
* 2、对ip地址排序 (看情况讲)
*
* 192.168.10.55 127.0.0.1 3.3.3.3 105.7.11.65
*/
public static void test2() {
String ipstr = "192.168.10.55 127.0.0.1 3.3.3.3 105.7.11.65";
// 方式二
// 1.为了让ip可以按照字符串顺序比较,只要让ip的每一段的位数相同。
// 所以,补零,按照每一位所需做多0进行补充,每一段都加两个0.
ipstr = ipstr.replaceAll("(\\d+)", "00$1");
System.out.println(ipstr);
//00192.00168.0010.0055 00127.000.000.001 003.003.003.003 00105.007.0011.0065
// 然后每一段保留数字3位
ipstr = ipstr.replaceAll("0*(\\d{3})", "$1");
System.out.println(ipstr);
// 方式一
// 1.将ip地址切出
String[] ips = ipstr.split(" +");
TreeSet<String> ts = new TreeSet<String>();
// Arrays.sort(ips);
for (String ip : ips) {
System.out.println(ip);
ts.add(ip);
}
for (String ip : ts) {
System.out.println(ip);
System.out.println("---"+ip.replaceAll("0*(\\d+)", "$1"));
}
}
/**
* 对邮箱地址校验 (必讲)
*/
public static void test3(){
String mail = "abc1@sina.com";
String regex = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]{1,3})+";
//regex = "\\w+@\\w+(\\.\\w+)+";// 1@1.1
boolean b = mail.matches(regex);
System.out.println(mail+" : "+b);
}
}
2.5、案例5
需求:爬虫 (需要自做一个静态的网页包含邮箱地址)-- (看情况讲解/代码直接讲解演示)
网页爬虫:其实就是一个程序用于在互联网中获取符合指定规则的数据。 爬取邮箱地址
版本一:本地版 – mail.html放在本地硬盘目录即可
版本二:网络版 – mail.html 部署在tomcat中 演示 模拟网络获取网页中的邮箱地址
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*
- 网页爬虫:其实就是一个程序用于在互联网中获取符合指定规则的数据。 爬取邮箱地址
- (需要自做一个静态的网页包含邮箱地址)
*/
public class RegexTest2 {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
List<String> list = getMails();
for (String mail : list) {
System.out.println(mail);
}
}
public static List<String> getMails() throws IOException {
List<String> list = new ArrayList<String>();
// 1.读取源文件
BufferedReader br = new BufferedReader(new FileReader("c:\\mail.html"));
// 2.对读取的数据进行规则的匹配。从中获取符合规则的数据。
String mailRegex = "\\w+@\\w+(\\.\\w+)+";
Pattern p = Pattern.compile(mailRegex);
String line = null;
while ((line = br.readLine()) != null) {
Matcher m = p.matcher(line);
while (m.find()) {
// 3.将符合规则的数据存储到集合中
list.add(m.group());
}
}
return list;
}
public static List<String> getMailsByweb() throws IOException {
List<String> list = new ArrayList<String>();
// 1.读取源文件
//BufferedReader br = new BufferedReader(new FileReader("c:\\mail.html"));
URL url = new URL("http://12.168.1.100:8080/myweb/mail.html");
//URL url = new URL("http://renshi.nwpu.edu.cn/rczp/index.html");
URL url = new URL("http://www.lhtit.com/Contactus/index.html");
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
// 2.对读取的数据进行规则的匹配。从中获取符合规则的数据。
String mailRegex = "\\w+@\\w+(\\.\\w+)+";
Pattern p = Pattern.compile(mailRegex);
String line = null;
while ((line = br.readLine()) != null) {
Matcher m = p.matcher(line);
while (m.find()) {
// 3.将符合规则的数据存储到集合中
list.add(m.group());
}
}
return list;
}
}
2.6、案例6
需求:统计java文件中的 空行 代码行 注释 数量 (看情况讲解/代码直接讲解演示)
package com.sxt.javaregex.demo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
- 统计指定文件夹下的java源文件的代码行数
*/
public class CodeCounter {
static long normalLines = 0;//正常行
static long commentLines = 0;//注释行
static long whiteLines = 0;//空白行
public static void main(String[] args) {
//1.获得文件夹
File f = new File("C:\\javacode");
//2.获得子文件夹中的 java文件
File[] codeFiles = f.listFiles();
for(File child : codeFiles){
if(child.getName().matches(".*\\.java$")) {// $代表结尾
parse(child);
}
}
System.out.println("normalLines:" + normalLines);
System.out.println("commentLines:" + commentLines);
System.out.println("whiteLines:" + whiteLines);
}
private static void parse(File f) {
BufferedReader br = null;
boolean comment = false;
try {
br = new BufferedReader(new FileReader(f));
String line = "";
while((line = br.readLine()) != null) {
line = line.trim();//去掉 注释前的 tab换行
if(line.matches("^[\\s&&[^\\n]]*$")) {//匹配空白行
whiteLines ++;
} else if (line.startsWith("/*") && !line.endsWith("*/")) {//匹配注释开始
commentLines ++;
comment = true;
} else if (line.startsWith("/*") && line.endsWith("*/")) {
commentLines ++;
} else if (true == comment) {
commentLines ++;
if(line.endsWith("*/")) {//匹配注释结束
comment = false;
}
} else if (line.startsWith("//")) {
commentLines ++;//匹配单行注释
} else {
normalLines ++;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br != null) {
try {
br.close();
br = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
总结
正则表达式 不要刻意去记 要理解