Java的正则表达式深入分析
程序员文章站
2023-12-03 09:24:40
一.regex(正则表达式):regularexpressions(代替了stringtokenizer);字符串处理利器;在unix流行,perl使用regex更牛。主要...
一.regex(正则表达式):regularexpressions(代替了stringtokenizer);字符串处理利器;在unix流行,perl使用regex更牛。
主要用在字符串匹配、查找和替换。例如:匹配ip(范围小于256)使用正则很好搞;从网页中揪出大量email地址发送垃圾邮件;从网页里揪出链接。包含matcher(用模式匹配字符串后产生的结果)和pattern。
复制代码 代码如下:
/*
* 告知此字符串是否匹配给定的正则表达式(也是一个字符串)。
*/
system.out.println("abc".matches("..."));//每个"."表示一个字符
复制代码 代码如下:
/*
* 把字符串里的所有数字替换成"-",普通方法需要charat逐个判断;
* "\\d"表示任意一个数字或者换成"[0-9]";
* "\\d"表示任意一个非数字或者换成"[^0-9]"
*/
system.out.println("ab54564654sbg48746bshj".replaceall("[0-9]", "-"));//每个"."表示一个字符
二、
复制代码 代码如下:
/*
* compile将给定的正则表达式编译到模式中(每次编译需要费时间);{3}表示恰好三次。
* x{n} x,恰好 n 次
* x{n,} x,至少 n 次
* x{n,m} x,至少 n 次,但是不超过 m 次
*/
pattern p = pattern.compile("[a-z]{3}");
matcher m = p.matcher("ggs");//创建匹配给定输入与此模式的匹配器。内部实际上是创建了一个优先状态的自动机(编译原理)
//matcher和matches里待匹配的字符串实际上是charsequence(接口),不过string实现了该接口,存在多态
system.out.println(m.matches());//若是"ggss"就不匹配了
//可一直接"ggs".matches("[a-z]{3}"),不过上面的有好处,至少效率高了,而且pattern和matcher提供了很多功能
三、在regex“. * +”中叫meta character;ctrl + shift + "/"表示注释,换成"\"表示去掉注释。
复制代码 代码如下:
"a".matches(".");//true,"."表示任意一个字符,汉字也行
"aa".matches("aa");//true,也就是说普通字符串也可以作为正则表达式
/*
* true,"*"表示0或者多个字符,不过后面的要和第一个相同,
* 否则false,也就是判断字符串是否是单一字符组成的字符串
*/
"aaaa".matches("a*");
"".matches("a*");//true
"aaa".matches("a?");//true,一次或者0次
"".matches("a?");//true
"a".matches("a?");//true
"544848154564113".matches("\\d{3,100}");//true
//这个是最简单的ip判断,不过若是超过255则判断不出来
"192.168.0.aaa".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\d{1,3}");
"192".matches("[0-2][0-9][0-9]");
四、[abc]表示匹配任意一个字符;[^abc]表示出了abc以外的其他字母(必须还是字母,若是空串也返回false)字符;[a-za-z]等价于"[a-z]|[a-z]"是否是某个大小写字母;[a-z&&[abs]]表示大写字母中取abs中任一个。
复制代码 代码如下:
//发现|和||没区别,&和&&有区别,不知道这么理解对不对
system.out.println("c".matches("[a-z&&[abs]]"));//false
system.out.println("c".matches("[a-z&[abs]]"));//true
system.out.println("a".matches("[a-z&&[abs]]"));//true
system.out.println("a".matches("[a-z&[abs]]"));//true
system.out.println("c".matches("[a-z|[abs]]"));//true
system.out.println("c".matches("[a-z||[abs]]"));//true
五、\w 单词字符:[a-za-z_0-9] 进行用户名匹配时;\s 空白字符:[ \t\n\x0b\f\r]; \s 非空白字符:[^\s] ;\w 非单词字符:[^\w] 。
复制代码 代码如下:
" \n\t\r".matches("\\s{4}");//true
" ".matches("\\s");//false
"a_8".matches("\\w{3}");//true
//“+”表示一次或者多次
"abc888&^%".matches("[a-z]{1,3}\\d+[&^#%]+");//true
/*
* 待匹配字符也只是一个反斜线,不过不可写成"\"那么和后面的"组合了,
* 前面的"无法匹配就会ce。
* 后面不可写成"\\",那么会运行错误(编译没问题),必须写成"\\\\"
*/
system.out.println("\\".matches("\\\\"));//true
六、posix 字符类(仅 us-ascii)
复制代码 代码如下:
\p{lower} 小写字母字符:[a-z] ;\p{upper} 大写字母字符:[a-z] ;\p{ascii} 所有 ascii:[\x00-\x7f] ;\p{alpha} 字母字符:[\p{lower}\p{upper}] ;\p{digit} 十进制数字:[0-9] 。
七、边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
\b 非单词边界
\a 输入的开头
\g 上一个匹配的结尾
\z 输入的结尾,仅用于最后的结束符(如果有的话)
\z 输入的结尾
复制代码 代码如下:
"hello world".matches("^h.*");//^行的开头
"hello world".matches(".*ld$");//$行的结尾
"hello world".matches("^h[a-z]{1,3}o\\b.*");//\b单词边界
"helloworld".matches("^h[a-z]{1,3}o\\b.*");
" \n".matches("^[\\s&&[^\\n]]*\\n$");//判断空白行,空白行开头是空白符
八、还可以在find方法下使用m.start()和m.end()返回开始位置和结束位置的下一个;若是找不到则出错。
复制代码 代码如下:
pattern p = pattern.compile("\\d{3,5}");
string s = "133-34444-333-00";
matcher m = p.matcher(s);
m.matches();//matches匹配全部字符串
m.reset();
/*
* 下面若是先调用了reset方法则输出true,true,true,false.
* 否则倒数第二个find也输出false。
* 原因如下:
* matches匹配到第一个"-"发现不匹配了,但是这四个字符已经被吃掉啦,再次匹配就从
* 34444开始了,第二个find从333,因为find匹配的是下一个子序列。
* reset方法让matches吃掉的字符串再吐出来。
* 综上:matches和find之间要使用reset,因为二者相互影响
*
*/
m.find();
m.find();
m.find();//尝试查找与该模式匹配的输入序列的下一个子序列
m.find();
/*
* 尝试将从区域开头开始的输入序列与该模式匹配。
* thinking in java的作者狠狠滴批评了这个方法,因为从字面看不出来到底从哪开始匹配。
* 下面全部是true,因为每次都从头开始
*/
m.lookingat();
m.lookingat();
m.lookingat();
m.lookingat();
九、字符串替换
复制代码 代码如下:
import java.util.regex.matcher;
import java.util.regex.pattern;
public class testregexreplacement {
public static void main(string[] args) {
pattern p = pattern.compile("java",pattern.case_insensitive);//后面的参数是整形,表示“大小写不敏感”
matcher m = p.matcher("java java hxsyl ilovejava java javaacmer");
while(m.find()) {
system.out.println(m.group());//m.group会输出所有的java(忽略大小写)
}
string s = m.replaceall("java");//string也有该方法
system.out.println(s);
m.reset();//一定要加,因为find和matcher相互影响
stringbuffer sb = new stringbuffer();
int i = 0;
/*
* 下面的方法是把找到的奇数个java替换为“java”,偶数个替换成"java"
*/
while(m.find()) {
i++;
//不能直接写成i&1必须转化为boolean
if((i&1)==1) {
m.appendreplacement(sb, "java");
}else {
m.appendreplacement(sb, "java");
}
}
m.appendtail(sb);//把找到的最后一个java后边的剩余字符串加上
system.out.println(sb);//不加reset的话只输出了acmer
}
}
十、分组
复制代码 代码如下:
/*
* 分别加上小括号,不算最外边的大括号,第一个左括号便是第一组
*/
pattern p = pattern.compile("(\\d{3,5})([a-z]{2})");
string s = "123aaa-77878bb-646dd-00";
matcher m = p.matcher(s);
while(m.find()) {
system.out.println(m.group());
system.out.println(m.group(1));//输出每对符合的 数字
system.out.println(m.group(2));//输出每对符合的 字母
}
十一、抓取网页中的email
复制代码 代码如下:
import java.io.bufferedreader;
import java.io.filenotfoundexception;
import java.io.filereader;
import java.io.ioexception;
import java.util.regex.matcher;
import java.util.regex.pattern;
/*
* 需要什么养的方法的话先些方法名
* 然后ctrl + 1列出推荐,系统创建该方法
*/
public class emailspider {
public static void main(string[] args) {
// todo auto-generated method stub
try {
bufferedreader br = new bufferedreader(new filereader("f:\\regex.html"));
string line = "";
try {
while((line=br.readline())!=null) {
solve(line);
}
} catch (ioexception e) {
// todo auto-generated catch block
e.printstacktrace();
}
} catch (filenotfoundexception e) {
// todo auto-generated catch block
e.printstacktrace();
}
}
private static void solve(string line) {
// todo auto-generated method stub
//正则表达式要是不满足相应功能的话不会出错,因为他是字符串
pattern p = pattern.compile("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+");
matcher m = p.matcher(line);
while(m.find()) {
system.out.println(m.group());
}
}
}
十二、代码统计
复制代码 代码如下:
view code
/*
* 统计代码里多少空行,注释行,程序行
* 实际上使用string里的startswith和endswith也行.
* 若是项目经理用的话还要统计每行的字符数是否以{;结尾,防止偷懒
*/
import java.io.bufferedreader;
import java.io.file;
import java.io.filenotfoundexception;
import java.io.filereader;
import java.io.ioexception;
public class codercount {
static long normallines = 0;
static long commentlines = 0;
static long whitelines = 0;
public static void main(string[] args) {
file f = new file("d:\\share\\src");
file[] codefiles = f.listfiles();
for(file child : codefiles){
if(child.getname().matches(".*\\.java$")) {
solve(child);
}
}
system.out.println("normallines:" + normallines);
system.out.println("commentlines:" + commentlines);
system.out.println("whitelines:" + whitelines);
}
private static void solve(file f) {
bufferedreader br = null;
boolean comment = false;
try {
br = new bufferedreader(new filereader(f));
string line = "";
while((line = br.readline()) != null) {
/*
* //有的注释行前面有一个tab
* 不可写在readline后
* 最后一行的话会空指针
*/
line = line.trim();
//readline读出字符串后就把后面的换行去掉啦
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();
}
}
}
}
}
十三、quantifiers
包括?*+;默认全是greedy,还有reluctant和possessive(独占性的)。
复制代码 代码如下:
//加上分组是为了看得更清晰一些
pattern p = pattern.compile("(.{3,10})+[0-9]");
string s = "aaaa5bbbb6";//长度是10
matcher m = p.matcher(s);
/*
* 现在输出0-10,默认是greedy,先吞进10个字符,发现不匹配,吐出来一个,发现匹配了;
* 若是pattern.compile("(.{3,10}?)+[0-9]")则成了reluctant,那么是先吞进三个字符,发现不匹配,继续吞入 知道匹配,输出0到5;
* 若是pattern.compile("(.{3,10}++)+[0-9]")则是possessive(独占式),也是先吞入10个字符,但是不向外吐,那么就不匹配了,
* 这种方式主要用在需要高效率的地方(会有误差)。
*/
if(m.find()) {
system.out.println(m.start() + "----" + m.end());
}else {
system.put.println("not match!");
}
十四、补充(非捕获组)
复制代码 代码如下:
//非捕获组的意思和字面相反,意思是若是符合则捕获
pattern p = pattern.compile("(?=a).{3}");
/*
* 输出a66,相当于要求以a开头,也可以这么写pattern.compile("[a].{2}");
* 若是pattern.compile(".{3}(?!=a)")不是不以a结尾{2}[^a],而是下一个字符不是a(lookahead),输出44a,66b,所以这种用法不常用;
* 若是pattern.compile(".{3}(?=a)")则输出444(因为?=a是lookahead),放在前面则包含在组内,后面则不包含在组内;
*
*
*/
string s = "444a66b";
matcher m = p.matcher(s);
while(m.find()) {
system.out.println(m.group());
}
十五、back reference
复制代码 代码如下:
pattern p = pattern.compile("(\\d\\d)\\1");
/*
* 输出true,\\1表示和第一个组的一样,若改成1213就不对了;
* 若是pattern.compile("(\\d(\\d))\\2")则需改成122才对
*
*/
string s = "1212";
matcher m = p.matcher(s);
system.out.println(m.matches());
十六、flags的简写
"."是不匹配换行的,记住case_insensitive就行了,简写“通过嵌入式标志表达式 (?i) 也可以启用不区分大小写的匹配”。
上一篇: C#中的协变与逆变深入讲解