深入浅解正则表达式在Java中的使用
介绍
•正则表达式一般用于字符串匹配,字符串查找和字符串替换.别小看它的作用,在工作学习中灵活运用正则表达式处理字符串能够大幅度提高效率,编程的快乐来得就是这么简单.
•一下子给出一堆匹配的规则可能会让人恐惧,下面将由浅入深讲解正则表达式的使用.
从简单例子认识正则表达式匹配
•先上代码
publicclassDemo1{
publicstaticvoidmain(String[]args){
//字符串abc匹配正则表达式"...",其中"."表示一个字符
//"..."表示三个字符
System.out.println("abc".matches("..."));
System.out.println("abcd".matches("..."));
}
}
//输出结果
true
false
•String类中有个matches(Stringregex)方法,返回值为布尔类型,用于告诉这个字符串是否匹配给定的正则表达式.
•在本例中我们给出的正则表达式为...,其中每个.表示一个字符,整个正则表达式的意思是三个字符,显然当匹配abc的时候结果为true,匹配abcd时结果为false.
Java中对正则表达式的支持(各种语言有相应的实现)
•在java.util.regex包下有两个用于正则表达式的类,一个是Matcher类,另一个Pattern.Java官方文档中给出对这两个类的典型用法,代码如下:
publicclassDemo1{
publicstaticvoidmain(String[]args){
//字符串abc匹配正则表达式"...",其中"."表示一个字符
//"..."表示三个字符
System.out.println("abc".matches("..."));
System.out.println("abcd".matches("..."));
}
}
//输出结果
true
false
•如果要深究正则表达式背后的原理,会涉及编译原理中自动机等知识,此处不展开描述.为了达到通俗易懂,这里用较为形象的语言描述.
•Pattern可以理解为一个模式,字符串需要与某种模式进行匹配.比如Demo2中,我们定义的模式是一个长度为3的字符串,其中每个字符必须是a~z中的一个.
•我们看到创建Pattern对象时调用的是Pattern类中的compile方法,也就是说对我们传入的正则表达式编译后得到一个模式对象.而这个经过编译后模式对象,会使得正则表达式使用效率会大大提高,并且作为一个常量,它可以安全地供多个线程并发使用.
•Matcher可以理解为模式匹配某个字符串后产生的结果.字符串和某个模式匹配后可能会产生很多个结果,这个会在后面的例子中讲解.
•最后当我们调用m.matches()时就会返回完整字符串与模式匹配的结果
•上面的三行代码可以简化为一行代码
System.out.println("abc".matches("[a-z]{3}"));
•但是如果一个正则表达式需要被重复匹配,这种写法效率较低.
初步认识.+*?
•在介绍之前首先要说明的是,正则表达式的具体含义不用强背,各个符号的含义在Java官方文档的Pattern类描述中或网上有详细的定义.当然能熟用就更好了.
publicclassDemo3{
/**
*为了省略每次写打印语句,这里把输出语句封装起来
*@paramo
*/
privatestaticvoidp(Objecto){
System.out.println(o);
}
/**
*.Anycharacter(mayormaynotmatchlineterminators),任意字符
*X?X,onceornotatall零个或一个
*X*X,zeroormoretimes零个或多个
*X+X,oneormoretimes一个或多个
*X{n}X,exactlyntimesx出现n次
*X{n,}X,atleastntimesx出现至少n次
*X{n,m}X,atleastnbutnotmorethanmtimes出现n~m次
*@paramargs
*/
publicstaticvoidmain(String[]args){
p("a".matches("."));
p("aa".matches("aa"));
p("aaaa".matches("a*"));
p("aaaa".matches("a+"));
p("".matches("a*"));
p("a".matches("a?"));
//\dAdigit:[0-9],表示数字,但是在java中对"\"这个符号需要使用\进行转义,所以出现\\d
p("2345".matches("\\d{2,5}"));
//\\.用于匹配"."
p("192.168.0.123".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"));
//[0-2]指必须是0~2中的一个数字
p("192".matches("[0-2][0-9][0-9]"));
}
}
//输出结果
//全为true
范围
•[]用于描述一个字符的范围,下面是一些例子
publicclassDemo4{
privatestaticvoidp(Objecto){
System.out.println(o);
}
publicstaticvoidmain(String[]args){
//[abc]指abc中的其中一个字母
p("a".matches("[abc]"));
//[^abc]指除了abc之外的字符
p("1".matches("[^abc]"));
//a~z或A~Z的字符,以下三个均是或的写法
p("A".matches("[a-zA-Z]"));
p("A".matches("[a-z|A-Z]"));
p("A".matches("[a-z[A-Z]]"));
//[A-Z&&[REQ]]指A~Z中并且属于REQ其中之一的字符
p("R".matches("[A-Z&&[REQ]]"));
}
}
//输出结果
全部为true
认识\s\w\d-下面介绍数字和字母的正则表达,这是编程中使用最多的字符了.
关于\
•这里重点介绍最不好理解的\.在Java中的字符串中,如果要用到特殊字符,必须通过在前面加\进行转义.
•举个例子,考虑这个字符串"老师大声说:"同学们,快交作业!"".如果我们没有转义字符,那么开头的双引号的结束应该在说:"这里,但是我们的字符串中需要用到双引号,所以需要用转义字符
•使用转义字符后的字符串为"老师大声说:\"同学们,快交作业!\"",这样我们的原意才能被正确识别.
•同理如果我们要在字符串中使用\,也应该在前面加一个\,所以在字符串中表示为"\\"
•那么如何在正则表达式中表示要匹配\呢,答案为"\\\\".
•我们分开考虑:由于正则式中表示\同样需要转义,所以前面的\\表示正则表达式中的转义字符\,后面的\\表示正则表达式中\本身,合起来在正则表达式中表示\.
•如果感觉有点绕的话请看下面代码
publicclassDemo5{
privatestaticvoidp(Objecto){
System.out.println(o);
}
publicstaticvoidmain(String[]args){
/**
*\dAdigit:[0-9]数字
*\DAnon-digit:[^0-9]非数字
*\sAwhitespacecharacter:[\t\n\x0B\f\r]空格
*\SAnon-whitespacecharacter:[^\s]非空格
*\wAwordcharacter:[a-zA-Z_0-9]数字字母和下划线
*\WAnon-wordcharacter:[^\w]非数字字母和下划线
*/
//\\s{4}表示4个空白符
p("\n\r\t".matches("\\s{4}"));
//\\S表示非空白符
p("a".matches("\\S"));
//\\w{3}表示数字字母和下划线
p("a_8".matches("\\w{3}"));
p("abc888&^%".matches("[a-z]{1,3}\\d+[%^&*]+"));
//匹配\
p("\\".matches("\\\\"));
}
}
//输出结果
全部为true
边界处理
•^在中括号内表示取反的意思[^],如果不在中括号里则表示字符串的开头.
publicclassDemo6{
privatestaticvoidp(Objecto){
System.out.println(o);
}
publicstaticvoidmain(String[]args){
/**
*^Thebeginningofaline一个字符串的开始
*$Theendofaline字符串的结束
*\bAwordboundary一个单词的边界,可以是空格,换行符等
*/
p("hellosir".matches("^h.*"));
p("hellosir".matches(".*r$"));
p("hellosir".matches("^h[a-z]{1,3}o\\b.*"));
p("hellosir".matches("^h[a-z]{1,3}o\\b.*"));
}
}
练习:匹配空白行合email地址
•拿到一篇文章,如何判断里面有多少个空白行?用正则表达式能方便地进行匹配,注意空白行中可能包括空格,制表符等.
p("\n".matches("^[\\s&&[^\n]]*\\n$"));
•解释:^[\\s&&[^\n]]*是空格符号但不是换行符,\\n$最后以换行符结束
•下面是匹配邮箱
p("liuyj24@126.com".matches("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+"));
•解释:[\\w[.-]]+以一个或多个数字字母下划线.或-组成,@接着是个@符号,然后同样是[\\w[.-]]+,接着\\.匹配.,最后同样是[\\w]+
Matcher类的matches(),find()和lookingAt()
•matches()方法会将整个字符串与模板进行匹配.
•find()则是从当前位置开始进行匹配,如果传入字符串后首先进行find(),那么当前位置就是字符串的开头,对当前位置的具体分析可以看下面的代码示例
•lookingAt()方法会从字符串的开头进行匹配.
publicclassDemo8{
privatestaticvoidp(Objecto){
System.out.println(o);
}
publicstaticvoidmain(String[]args){
Patternpattern=Pattern.compile("\\d{3,5}");
Strings="123-34345-234-00";
Matcherm=pattern.matcher(s);
//先演示matches(),与整个字符串匹配.
p(m.matches());
//结果为false,显然要匹配3~5个数字会在-处匹配失败
//然后演示find(),先使用reset()方法把当前位置设置为字符串的开头
m.reset();
p(m.find());//true匹配123成功
p(m.find());//true匹配34345成功
p(m.find());//true匹配234成功
p(m.find());//false匹配00失败
//下面我们演示不在matches()使用reset(),看看当前位置的变化
m.reset();//先重置
p(m.matches());//false匹配整个字符串失败,当前位置来到-
p(m.find());//true匹配34345成功
p(m.find());//true匹配234成功
p(m.find());//false匹配00始边
p(m.find());//false没有东西匹配,失败
//演示lookingAt(),从头开始找
p(m.lookingAt());//true找到123,成功
}
}
Matcher类中的start()和end()
•如果一次匹配成功的话start()用于返回匹配开始的位置,end()用于返回匹配结束字符的后面一个位置
publicclassDemo9{
privatestaticvoidp(Objecto){
System.out.println(o);
}
publicstaticvoidmain(String[]args){
Patternpattern=Pattern.compile("\\d{3,5}");
Strings="123-34345-234-00";
Matcherm=pattern.matcher(s);
p(m.find());//true匹配123成功
p("start:"+m.start()+"-end:"+m.end());
p(m.find());//true匹配34345成功
p("start:"+m.start()+"-end:"+m.end());
p(m.find());//true匹配234成功
p("start:"+m.start()+"-end:"+m.end());
p(m.find());//false匹配00失败
try{
p("start:"+m.start()+"-end:"+m.end());
}catch(Exceptione){
System.out.println("报错了...");
}
p(m.lookingAt());
p("start:"+m.start()+"-end:"+m.end());
}
}
//输出结果
true
start:0-end:3
true
start:4-end:9
true
start:10-end:13
false
报错了...
true
start:0-end:3
替换字符串
•想要替换字符串首先要找到被替换的字符串,这里要新介绍Matcher类中的一个方法group(),它能返回匹配到的字符串.
•下面我们看一个例子,把字符串中的java转换为大写.
publicclassDemo10{
privatestaticvoidp(Objecto){
System.out.println(o);
}
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile("java");
Matcherm=p.matcher("javaJavaJAVAJAvaIloveJavaandyou");
p(m.replaceAll("JAVA"));//replaceAll()方法会替换所有匹配到的字符串
}
}
//输出结果
JAVAJavaJAVAJAvaIloveJavaandyou
升级:不区分大小写查找并替换字符串
•为了在匹配的时候不区分大小写,我们要在创建模板模板时指定大小写不敏感
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile("java",Pattern.CASE_INSENSITIVE);//指定为大小写不敏感的
Matcherm=p.matcher("javaJavaJAVAJAvaIloveJavaandyou");
p(m.replaceAll("JAVA"));
}
//输出结果
JAVAJAVAJAVAJAVAIloveJAVAandyou
再升级:不区分大小写,替换查找到的指定字符串
•这里演示把查找到第奇数个字符串转换为大写,第偶数个转换为小写
•这里会引入Matcher类中一个强大的方法appendReplacement(StringBuffersb,Stringreplacement),它需要传入一个
StringBuffer进行字符串拼接.
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile("java",Pattern.CASE_INSENSITIVE);
Matcherm=p.matcher("javaJavaJAVAJAvaIloveJavaandyou?");
StringBuffersb=newStringBuffer();
intindex=1;
while(m.find()){
//m.appendReplacement(sb,(index++&1)==0?"java":"JAVA");较为简洁的写法
if((index&1)==0){//偶数
m.appendReplacement(sb,"java");
}else{
m.appendReplacement(sb,"JAVA");
}
index++;
}
m.appendTail(sb);//把剩余的字符串加入
p(sb);
}
//输出结果
JAVAjavaJAVAjavaIloveJAVAandyou?
分组
•先从一个问题引入,看下面这段代码
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile("\\d{3,5}[a-z]{2}");
Strings="123aa-5423zx-642oi-00";
Matcherm=p.matcher(s);
while(m.find()){
p(m.group());
}
}
//输出结果
123aa
5423zx
642oi
•其中正则表达式"\\d{3,5}[a-z]{2}"表示3~5个数字跟上两个字母,然后打印出每个匹配到的字符串.
•如果想要打印每个匹配串中的数字,如何操作呢.
•首先你可能想到把匹配到的字符串再进行匹配,但是这样太麻烦了,分组机制可以帮助我们在正则表达式中进行分组.
•规定使用()进行分组,这里我们把字母和数字各分为一组"(\\d{3,5})([a-z]{2})"
•然后在调用m.group(intgroup)方法时传入组号即可
•注意,组号从0开始,0组代表整个正则表达式,从0之后,就是在正则表达式中从左到右每一个左括号对应一个组.在这个表达式中第1组是数字,第2组是字母.
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile("(\\d{3,5})([a-z]{2})");//正则表达式为3~5个数字跟上两个字母
Strings="123aa-5423zx-642oi-00";
Matcherm=p.matcher(s);
while(m.find()){
p(m.group(1));
}
}
//输出结果
123
5423
642
实战1:抓取网页中的email地址(爬虫)
•假设我们手头上有一些优质的资源,打算分享给网友,于是便到贴吧上发出一个留邮箱发资源的帖子.没想到网友热情高涨,留下了近百个邮箱.但逐个复制发送太累了,我们考虑用程序实现.
•这里不展开讲发邮件部分,重点应用已经学到的正则表达式从网页中截取所有的邮箱地址.
•首先获取一个帖子的html代码随便找了一个,点击跳转,在浏览器中点击右键保存html文件
•接下来看代码:
publicclassDemo12{
publicstaticvoidmain(String[]args){
BufferedReaderbr=null;
try{
br=newBufferedReader(newFileReader("C:\\emailTest.html"));
Stringline="";
while((line=br.readLine())!=null){//读取文件的每一行
parse(line);//解析其中的email地址
}
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}finally{
if(br!=null){
try{
br.close();
br=null;
}catch(IOExceptione){
e.printStackTrace();
}
}
}
}
privatestaticvoidparse(Stringline){
Patternp=Pattern.compile("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+");
Matcherm=p.matcher(line);
while(m.find()){
System.out.println(m.group());
}
}
}
//输出结果
2819531636@qq.com
2819531636@qq.com
2405059759@qq.com
2405059759@qq.com
1013376804@qq.com
...
实战2:代码统计小程序
•最后的一个实战案例:统计一个项目中一共有多少行代码,多少行注释,多少个空白行.不妨对自己做过的项目进行统计,发现不知不觉中也是个写过成千上万行代码的人了...
•我在github上挑选了一个项目,是纯java写的小项目,方便统计.点击跳转
•下面是具体的代码,除了判断空行用了正则表达式外,判断代码行和注释行用了String类的api
publicclassDemo13{
privatestaticlongcodeLines=0;
privatestaticlongcommentLines=0;
privatestaticlongwhiteLines=0;
privatestaticStringfilePath="C:\\TankOnline";
publicstaticvoidmain(String[]args){
process(filePath);
System.out.println("codeLines:"+codeLines);
System.out.println("commentLines:"+commentLines);
System.out.println("whiteLines:"+whiteLines);
}
/**
*递归查找文件
*@parampathStr
*/
publicstaticvoidprocess(StringpathStr){
Filefile=newFile(pathStr);
if(file.isDirectory()){//是文件夹则递归查找
File[]fileList=file.listFiles();
for(Filef:fileList){
StringfPath=f.getAbsolutePath();
process(fPath);
}
}elseif(file.isFile()){//是文件则判断是否是.java文件
if(file.getName().matches(".*\\.java$")){
parse(file);
}
}
}
privatestaticvoidparse(Filefile){
BufferedReaderbr=null;
try{
br=newBufferedReader(newFileReader(file));
Stringline="";
while((line=br.readLine())!=null){
line=line.trim();//清空每行首尾的空格
if(line.matches("^[\\s&&[^\\n]]*$")){//注意不是以\n结尾,因为在br.readLine()会去掉\n
whiteLines++;
}elseif(line.startsWith("/*")||line.startsWith("*")||line.endsWith("*/")){
commentLines++;
}elseif(line.startsWith("//")||line.contains("//")){
commentLines++;
}else{
if(line.startsWith("import")||line.startsWith("package")){//导包不算
continue;
}
codeLines++;
}
}
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}finally{
if(null!=br){
try{
br.close();
br=null;
}catch(IOExceptione){
e.printStackTrace();
}
}
}
}
}
//输出结果
codeLines:1139
commentLines:124
whiteLines:172
贪婪模式与非贪婪模式
•经过两个实战后,相信大家已经掌握了正则表达式的基本使用了,下面介绍贪婪模式与非贪婪模式.通过查看官方api我们发现Pattern类中有如下定义:
Greedyquantifiers贪婪模式
X? X,onceornotatall
X* X,zeroormoretimes
X+ X,oneormoretimes
X{n} X,exactlyntimes
X{n,} X,atleastntimes
X{n,m} X,atleastnbutnotmorethanmtimes
Reluctantquantifiers非贪婪模式(勉强的,不情愿的)
X??X,onceornotatall
X*?X,zeroormoretimes
X+?X,oneormoretimes
X{n}? X,exactlyntimes
X{n,}? X,atleastntimes
X{n,m}?X,atleastnbutnotmorethanmtimes
Possessivequantifiers 独占模式
X?+X,onceornotatall
X*+X,zeroormoretimes
X++X,oneormoretimes
X{n}+ X,exactlyntimes
X{n,}+ X,atleastntimes
X{n,m}+X,atleastnbutnotmorethanmtimes
•这三种模式表达的意思是一样的,在前面的讲解中我们全部使用的是贪婪模式.那么其他两种模式的写法有什么区别呢?通过下面的代码示例进行讲解.
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile(".{3,10}[0-9]");
Strings="aaaa5bbbb6";//10个字符
Matcherm=p.matcher(s);
if(m.find()){
System.out.println(m.start()+"-"+m.end());
}else{
System.out.println("notmatch!");
}
}
//输出结果
0-10
•正则表达式的意思是3~10个字符加一个数字.在贪婪模式下匹配时,系统会先吞掉10个字符,这时检查最后一个是否时数字,发现已经没有字符了,于是吐出来一个字符,再次匹配数字,匹配成功,得到0-10.
•下面是非贪婪模式演示(勉强的,不情愿的)
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile(".{3,10}?[0-9]");//添加了一个?
Strings="aaaa5bbbb6";
Matcherm=p.matcher(s);
if(m.find()){
System.out.println(m.start()+"-"+m.end());
}else{
System.out.println("notmatch!");
}
}
//输出结果
0-5
•在非贪婪模式下,首先只会吞掉3个(最少3个),然后判断后面一个是否是数字,结果不是,在往后吞一个字符,继续判断后面的是否数字,结果是,输出0-5
•最后演示独占模式,通常只在追求效率的情况下这么做,用得比较少
publicstaticvoidmain(String[]args){
Patternp=Pattern.compile(".{3,10}+[0-9]");//多了个+
Strings="aaaa5bbbb6";
Matcherm=p.matcher(s);
if(m.find()){
System.out.println(m.start()+"-"+m.end());
}else{
System.out.println("notmatch!");
}
}
//输出结果
notmatch!
•独占模式会一下吞进10个字符,然后判断后一个是否是数字,不管是否匹配成功它都不会继续吞或者吐出一个字符.
总结
以上所述是小编给大家介绍的正则表达式在Java中的使用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!