一、概述
正则表达式是Java处理字符串、文本的重要工具。
Java对正则表达式的处理集中在以下两个两个类:
java.util.regex.Matcher 模式类:用来表示一个编译过的正则表达式。
java.util.regex.Pattern 匹配类:用模式匹配一个字符串所表达的抽象结果。
(很遗憾,Java Doc并没有给出这两个类的职责概念。)
基本使用方式
(一)支持的基本通配符:
. -可以匹配任意字符
\s -代表一个任意空白(空格、Tab)。
\S -代表一个任意的非空白。
\d -代表一个任意的数字(digital)。
\D -代表一个任意的非数字。
\w -代表一个单词字符。
-W -代表一个任意的非单词字符
注意:对于特殊字符,实际使用时记住要转义\ ,如:( ) [ ] { } \ ? * + ^(一行的开头) $(一行的结尾)|
(二)取值范围(用作出现次数的“副词”)
? –代表它前面的东西可以出现0~1次
* –代表它前面的东西可以出现0~N次
+ –代表它前面的东西可以出现1~N次
{n,m} –代表它前面的东西可以出现n~m次
{n,} –代表它前面的东西至少出现n次
{,m} –代表它前面的东西最多出现m次
{n} –代表它前面的东西必须出现n次
(三)方括号表达式
枚举:[ab1] –代表a或b或者1。
范围:[a-c] –代表a,b,c中的任意一个字符。
枚举与范围:[a-c1-3]–代表a,b,c,1,2,3中的任意一个字符。
表示求否:[^a-c] –代表不含a,b,c其中任意一个字符。
表示求交:[a-g&&[^b-d]]:–代表a,e,f,g中的任意一个字符。
表示必须含有其中之一:(com|org|cn)
总结:一个字符用\,多个字符用[],字符次数用{}
比如一个简单例子:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class testRX
{
public static void main(String[] args)
{
Pattern p = Pattern.compile("f(.+?)k");
Matcher m = p.matcher("fckfkkfkf");
while (m.find())
{
String s0 = m.group();
String s1 = m.group(1);
System.out.println(s0 + "||" + s1);
}
System.out.println("---------");
m.reset("fucking!");
while (m.find())
{
System.out.println(m.group());
}
Pattern p1 = Pattern.compile("f(.+?)i(.+?)h");
Matcher m1 = p1.matcher("finishabigfishfrish");
while (m1.find())
{
String s0 = m1.group();
String s1 = m1.group(1);
String s2 = m1.group(2);
System.out.println(s0 + "||" + s1 + "||" + s2);
}
System.out.println("---------");
Pattern p3 = Pattern
.compile("(19|20)\\d\\d([- /.])(0[1-9]|1[012])\\2(0[1-9]|[12][0-9]|3[01])");
Matcher m3 = p3
.matcher("1900-01-01 2007/08/13 1900.01.01 1900 01 01 1900-01.01 1900 13 01 1900 02 31");
while (m3.find())
{
System.out.println(m3.group());
}
}
}
输出结果:
fck||c
fkk||k
---------
fuck
finish||in||s
fishfrish||ishfr||s
---------
1900-01-01
2007/08/13
1900.01.01
1900 01 01
1900 02 31
二、一些容易迷糊的问题
1、Java对反斜线处理的问题
在其他语言中,\表示要插入一个字符\;
在Java语言中,\表示要插入正则表达式的反斜线,并且后面的字符有特殊意义。
看API文档:
预定义字符类
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]
但是看看上面程序,对比下不难看出:
\d在实际使用的时候就写成了 \d;
在Java正则表达式中,如果要插入一个\字符,则需要在正则表达式中写成\\,原因是下面的APIDoc定义\表示一个反斜线。
但是如果在正则表示式中表示回车换行等,则不需要多添加反斜线了。比如回车\r就写作\r.
字符
x 字符 x
\ 反斜线字符
\0n 带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn 带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
\xhh 带有十六进制值 0x 的字符 hh
\uhhhh 带有十六进制值 0x 的字符 hhhh
\t 制表符 (‘\u0009’)
\n 新行(换行)符 (‘\u000A’)
\r 回车符 (‘\u000D’)
\f 换页符 (‘\u000C’)
\a 报警 (bell) 符 (‘\u0007’)
\e 转义符 (‘\u001B’)
\cx 对应于 x 的控制符
2、Matcher.find():
尝试查找与模式匹配的字符序列的下一个子序列。此方法从字符序列的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始,即如果前一次找到与模式匹配的子序列则这次从这个子序列后开始查找。
3、Matcher.matchers():
判断整个字符序列与模式是否匹配。当连续用Matcher对象检查多个字符串时候,可以使用
Matcher.reset():重置匹配器,放弃其所有显式状态信息并将其添加位置设置为零。
或者Matcher.reset(CharSequence input) 重置此具有新输入序列的匹配器。
来重复使用匹配器。
4、组的概念
这个概念很重要,组是用括号划分的正则表达式,可以通过编号来引用组。组号从0开始,有几对小括号就表示有几个组,并且组可以嵌套,组号为0的表示整个表达式,组号为1的表示第一个组,依此类推。
例如:A(B)C(D)E正则式中有三组,组0是ABCDE,组1是B,组2是D;
A((B)C)(D)E正则式中有四组:组0是ABCDE,组1是BC,组2是B;组3是C,组4是D。
int groupCount():返回匹配其模式中组的数目,不包括第0组。
String group():返回前一次匹配操作(如find())的第0组。
String group(int group):返回前一次匹配操作期间指定的组所匹配的子序列。如果该匹配成功,但指定组未能匹配字符序列的任何部分,则返回 null。
int start(int group):返回前一次匹配操作期间指定的组所匹配的子序列的初始索引。
int end(int group):返回前一次匹配操作期间指定的组所匹配的子序列的最后索引+1。
5、匹配的范围的控制
最变态的就要算lookingAt()方法了,名字很让人迷惑,需要认真看APIDoc。
start() 返回以前匹配的初始索引。
end() 返回最后匹配字符之后的偏移量。
public boolean lookingAt()尝试将从区域开头开始的输入序列与该模式匹配。
与 matches 方法类似,此方法始终从区域的开头开始;与之不同的是,它不需要匹配整个区域。
如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。
返回:
当且仅当输入序列的前缀匹配此匹配器的模式时才返回 true。
6、Pattern标记
Pattern类的静态方法
static Pattern compile(String regex, int flags)
将给定的正则表达式编译到具有给定标志的模式中。
其中的flags参数就是Pattern标记,这个标记在某些时候非常重要。
Pattern.CANON_EQ
启用规范等价。
Pattern.CASE_INSENSITIVE
启用不区分大小写的匹配。
Pattern.COMMENTS
模式中允许空白和注释。
Pattern.DOTALL
启用 dotall 模式。
Pattern.LITERAL
启用模式的字面值分析。
Pattern.MULTILINE
启用多行模式。
Pattern.UNICODE_CASE
启用 Unicode 感知的大小写折叠。
Pattern.UNIX_LINES
启用 Unix 行模式。
三、字符串的替换
String.replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 而生成的。
String.replace(CharSequence target, CharSequence replacement)
使用指定的字面值替换序列替换此字符串匹配字面值目标序列的每个子字符串。
String.replaceAll(String regex, String replacement)
使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的每个子字符串。
String.replaceFirst(String regex, String replacement)
使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的第一个子字符串。
StringBuffer.replace(int start, int end, String str)
使用给定 String 中的字符替换此序列的子字符串中的字符。
StringBuilder.replace(int, int, java.lang.String)
使用给定 String 中的字符替换此序列的子字符串中的字符。
Matcher.replaceAll(String replacement)
替换模式与给定替换字符串相匹配的输入序列的每个子序列。
Matcher.replaceFirst(String replacement)
替换模式与给定替换字符串匹配的输入序列的第一个子序列。
四、字符串的切分
String[] split(String regex)
根据给定的正则表达式的匹配来拆分此字符串。
String[] split(String regex, int limit)
根据匹配给定的正则表达式来拆分此字符串。
当然,还有一个StringTokenizer类,可以用来切分字符串,但是现在SUN已经不推荐使用了。
转变下思路,其实用正则表达式也可以达到将字符串切分为段的目的。
另附其他正则表达式例子
(一)基本用法演示:
public class RegexTest {
public static void main(String[] args) {
// 单个字符
System.out.println("a".matches("."));
// 0~1个a
System.out.println("a".matches("a?"));
// 1~N个a
System.out.println("aaaa".matches("a+"));
// 0~N个a
System.out.println("".matches("a*"));
// 1~N个q和1个0~9之间的数字
System.out.println("qqqqqq3".matches("q+[0-9]"));
// 12~100个数字
System.out.println("12345667890123".matches("\\d{12,100}"));
// 0~3个数字分别以.分割
System.out.println("192.168.0.1"
.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"));
// 第一个数字0~2,第二个数字0~9,第三个数字0~9
System.out.println("192".matches("[0-2][0-9][0-9]"));
// 4个任意空白
System.out.println(" \n\r\t".matches("\\s{4}"));
// 特殊字符\需转义
System.out.println("\\".matches("\\\\"));
// 以h开头,中间有0~N个字符,最后以o结尾
System.out.println("hello".matches("^.*o$"));
// 以h开头,中间1~3个字母尾随一个o,接着空白连着0~N个字符并以d结尾
System.out.println("hello world".matches("^h[a-z]{1,3}o\\b.*d$"));
// 以任意空白且不以换行开头为开头,并以换行结尾
System.out.println(" \n".matches("^[\\s&&[^\\n]]*\\n$"));
// 0~N个字符,连接4个数字和一个字符
System.out.println("aaa 2222q".matches(".*\\d{4}."));
}
}
(二)实际应用演示:
1、读取网页中所有的邮箱地址
public class EmailTest {
public static void main(String[] args) {
// 1~N个单词(可能含有.、-)连接 @1~N个单词连着 . 最后以com|org|cn|net其中之一结尾
String emailTemplate = "[\\w[.-]]+@[\\w]+\\.(com|org|cn|net)";
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(
"d:\\email.html")));
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
parse(sb.toString(), emailTemplate);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
}
/**
* 打印网页中的所有邮箱地址
*
* @param targetStr
* 目标字符串
* @param template
* 待编译的正则模板
*/
public static void parse(String targetStr, String template) {
if (targetStr == null || template == null) {
return;
}
// 获取编译好的待匹配的模板
Pattern pattern = Pattern.compile(template);
// 获取匹配目标字符串后产生的结果
Matcher matcher = pattern.matcher(targetStr);
// 若查找下一个匹配正则表达式的字符串
while (matcher.find()) {
// 则取出上一次与正则表达式匹配的字串。
System.out.println("=======" + matcher.group());
}
}
}
2、代码行数统计:
/**
* 代码统计:遍历某个项目的源文件的代码行数。
*
* 包括:空白行数、代码行数、注释行数。
*
*/
public class CodeCounter {
/**
* 空白行数
*/
private static long whiteLines = 0;
/**
* 代码行数
*/
private static long normalLines = 0;
/**
* 注释行数
*/
private static long commentLines = 0;
public static void main(String[] args) {
File srcDir = new File("D:\\workspace\\android\\Abc\\src");
myList(srcDir);// 遍历所java源文件
System.out.println("whiteLines = " + whiteLines);
System.out.println("normalLines = " + normalLines);
System.out.println("commentLines = " + commentLines);
System.out.println("totalLines = " + getTotalLines());
}
/**
* 获取总行数
*/
private static long getTotalLines() {
long value = whiteLines + normalLines + commentLines;
return value;
}
/**
* 遍历所java源文件
*/
private static void myList(File srcDir) {
System.out.println(srcDir + "目录下包含的目录和子文件有:");
File[] files = srcDir.listFiles();
for (File file : files) {
System.out.println("----------" + file);
if (file.getName().matches(".*\\.java$")) {
parse(file);
}
if (file.isDirectory()) {
myList(file);
}
}
}
/**
* 读取源文件内容
*
* @param file
* java文件
*/
private static void parse(File file) {
BufferedReader br = null;
/**
* 标识注释的开始或结束
*/
boolean comment = false;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(
file)));
String line = null;
while ((line = br.readLine()) != null) {
line = line.trim();
// 以任意空白且不以换行开头为开头,并以换行结尾
if (line.matches("^[\\s&&[^\\n]]*$")) {
whiteLines++;
} else if (line.startsWith("/*")) {
commentLines++;
comment = true;
} else if (comment == true) {
commentLines++;
if (line.endsWith("*/")) {
comment = false;
}
} else if (line.contains("//")) {
commentLines++;
} else {
normalLines++;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
br = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
(三)正则表达式进阶使用:
1、查找子串
String s1 = "123-45678-987-11";
Pattern pattern = Pattern.compile("\\d{3,5}"); // 匹配3~5个数字
Matcher matcher = pattern.matcher(s1);
System.out.println(matcher.matches());// false
matcher.reset();// 重置匹配器,将其添加位置设置为零
System.out.println(matcher.find());// true,由于重置了匹配器此时将从起始位置查找
System.out.println(matcher.start() + "-" + matcher.end());// 位置:0-3
// 与matches方法唯一不同的是lookingAt不需要匹配整个区域 ,它永远是从第一个子串开始
System.out.println(matcher.lookingAt());// true
System.out.println(matcher.lookingAt());// true
2、查找与替换
// CASE_INSENSITIVE:忽略子串大小写
Pattern pattern2 = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
Matcher matcher2 = pattern2
.matcher("java Java JAVA jaVA jAVA ILoveYouJaVA youhateJaVa");
// 将查找到的所有子串进行替换 (查找并替换)
System.out.println(matcher2.replaceAll("JAVA"));