正则表达式
关键字:正则表达式,Pattern,Matcher,字符串方法,split,replace
前文书立下了一个flag,这里要把它完成,就是正则表达式,它是一个工具,是很早就存在于标准Unix工具集之中的,例如sed和awk。然而不经常使用Unix系统的程序员们依然能够在JavaScript,java,python,perl等等地方看到它,每当我们看到手指纷飞的他人写着精妙的一小撮正则就干了我们好几篇的校验代码的时候,心里默默升起一股羡慕之情,同时只能赶紧把这一小撮正则保存下来,下次好修修补补继续用,那么,我们能否读懂正则表达式呢,不再把它们作为天书看待。
Java程序员普遍对与正则表达式并不算深入,因为我们有String以及StringBuilder,我们引以为傲的强大的java的字符串的处理在正则表达式的面前只能是小垃。
正则表达式是一种强大灵活的文本处理工具,通过它能够解决各种字符串处理相关的问题:匹配、选择、编辑以及验证,Java程序员们,扔掉split(),replace()以及subString()吧,每次通过他们的组合变来变去只为了实现一个我们不到一首诗的时间的需求,简直对计算机和对我们本人来讲都是一种消耗。
一、热身
首先介绍几个最基础的正则表达式热热身。
- 正则表达式使用反斜杠来转义特殊字符,java中使用两个反斜杠\
- ?: 一个或者没有
- +: 一个或者多个
- d: 数字
- (): 用括号分组,整体相当于一个单独的字符
- |: 代表或者,一般与括号分组一起使用
下面看具体代码,
测试方案
这里的测试方案是采用的之前io中文件名过滤器的那段代码。首先我们定义一个字符串数组作为源数据。
String[] data = { "a.txt", "+", "12345", "8", "-2", "-2123", "+010" };
这些字符串中基本包含了我们以下要练手的内容。接着,定义一个简单的获取数字的正则。
String regExp = "\d";// 一位数的正整数字符【只匹配一个】
然后下面使用这个正则的方式为:
for (String s : data) {
if (Pattern.matches(regExp, s))
logger.info(s);
}
意思为遍历字符串数组,如果匹配了该正则表达式,则打印出来(这里只做一个测试方案,具体Pattern以及Matcher在下面会有详细介绍)。这段代码的输出结果为:
09:21:02[testRegExp]: 8
开始测试
上面的测试方案中我们写入的正则表达式是“\d”,只匹配一位正整数字符。下面开始正式热身测试。
regExp = "\d+";// 正数数字【加号+代表一个或者多个】
09:33:48[testRegExp]: 12345
09:33:48[testRegExp]: 8
regExp = "-?\d+";// 数字(包括正数和负数)
09:34:27[testRegExp]: 12345
09:34:27[testRegExp]: 8
09:34:27[testRegExp]: -2
09:34:27[testRegExp]: -2123
regExp = "-\d+";// 所有负数【只匹配以一个字符‘-’开头的】
09:34:49[testRegExp]: -2
09:34:49[testRegExp]: -2123
regExp = "-\d";// 只有一位数的负数【只匹配以一个字符‘-’开头的,同时只有一位整数的字符串】
09:35:06[testRegExp]: -2
从上面这四段正则可以总结出来,假设有一个字符a,我们匹配它各种情况的正则表达式的方式为:
a?(一个或者没有) -> a(只有一个) -> a+(一个或者多个)
regExp = "\+";// 内容为加号的字符串
09:37:21[testRegExp]: +
regExp = "\+\d+";// 以加号开头接整数的字符串
09:37:59[testRegExp]: +010
这两段的意思是我们要匹配加号,但加号本身又是正则表达式的一部分运算符,所以要加\来将其转义为普通字符。
regExp = "(\+|-)\d+";// 以一个加号或者一个负号开头的数字
09:39:29[testRegExp]: -2
09:39:29[testRegExp]: -2123
09:39:29[testRegExp]: +010
regExp = "(\+|-)?\d+";// 以一个加号或者一个负号开头或者没有符号的数字
09:39:47[testRegExp]: 12345
09:39:47[testRegExp]: 8
09:39:47[testRegExp]: -2
09:39:47[testRegExp]: -2123
09:39:47[testRegExp]: +010
这两段我们使用了括号用来表示一个分组,整个括号内容相当于一个单独的字符。
String方法中的正则表达式
上面提到过java字符串中的split,replace等方法,他们是支持正则表达式的,所以不要只用他们的字符串简单操作,划分替换的部分,还可以应用一下正则表达式,正则表达式给了我们编程上一个模糊查询的作用,对比起来直接使用字符串本身作为搜索参数,一个正则表达式可以代表的内容更加丰富。
- split方法
String regex = " ";// 按空格来划分字符串
regex = "\W+";// 正则选择出非单词字符,split过滤一遍以后剩下纯单词,删除其他符号
regex = "g\W+";// 字母n后面跟着非单词字符,也就是‘n后面有空格字符’,split以后就去掉了n和这个空格
String preface = "I wish I had this book when I started programming... I recommend this book to every student as well as beginner and intermediate Java programmer.";
String[] a = preface.split(regex);
logger.info(a.length + " " + Arrays.toString(a));
11:03:17[testSplit]: 2 [I wish I had this book when I started programmin, I recommend this book to every student as well as beginner and intermediate Java programmer.]
- replace方法
logger.info(preface.replaceFirst("p\w+", "heyhey"));// 将第一个p打头的单词改为heyhey
logger.info(preface.replaceAll("p\w+", "heyhey"));// 将全部p打头的单词改为heyhey
11:03:17[testSplit]: I wish I had this book when I started heyhey... I recommend this book to every student as well as beginner and intermediate Java programmer.
11:03:17[testSplit]: I wish I had this book when I started heyhey... I recommend this book to every student as well as beginner and intermediate Java heyhey.
具体解释请直接看代码中的注释。
二、java.util.regex
前面的正则表达式都是一个字符串对象,然而java支持了专门的更加强大的正则表达式相关的类。下面来介绍如何在java中创建一个正则表达式,主要是在java.util.regex中的Pattern类和Matcher类。
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
logger.info(b);
输出:true
如果不涉及模式重用、匹配器重用,可以改为
logger.info(Pattern.matches("a*b", "aaaab"));
11:22:59[testRegex]: true
上面介绍过?,无符号和+,分别代表一个或没有,一个,一个或多个,那么有没有一个符号可以覆盖它们所有情况,代表没有或有,一个或多个呢?
- *: 含有一个或多个该字符,或者不含有该字符
而且一般正则表达式是小写字母代表本身意思,它的大写代表相反的意思,例如上面提到的,
- w: 词字符,大小写字母加数字
- W: 非词字符
- ^: 非
所以W也可以表示为[^w],同样的,
- d: 匹配数字[0-9]
- D: 非数字[^0-9]
- []: 方括号,表示其中的任意字符,相当于括号加|的分组
- s: 空白符(空格、tab、换行、换页或回车)
- S: 非空白符[^s]
接着,再说几种方括号内部的情况,
- [abc]: 包含abc任意字符的单个字符(注意只有一个字符)
Pattern p = Pattern.compile("[abc]");
logger.info(p.matcher("abc").matches());
logger.info(p.matcher("b").matches());
logger.info(p.matcher("ab").matches());
logger.info(p.matcher("c").matches());
14:15:57[testRegex]: false
14:15:57[testRegex]: true
14:15:57[testRegex]: false
14:15:57[testRegex]: true
[abc]与(a|b|c)的效果是相同的。
- [a-zA-Z]: 同样是单个字符,字符范围是大小写字母。
方括号如果不加?,*或者+,效果与无符号的单个字符一样,代表仅匹配一个字符。
- [abc[hij]]: 同样是匹配单个字符,范围是abchij,并集。
- [a-z&&[hcj]]: 取的是交集,hcj在a-z的范围内,所以最终意思为匹配一个字符,字符范围在hcj任意一个。
数量的设定
正则表达式可以约定字符出现的次数,上面的?,*,+都已经提到了,那么具体的量词如何设置呢?
Pattern p = Pattern.compile("[a-z]{3}");
logger.info(p.matcher("abc234").matches());
logger.info(p.matcher("ccc").matches());
logger.info(p.matcher("ab").matches());
logger.info(p.matcher("c").matches());
15:12:49[testRegex]: false
15:12:49[testRegex]: true
15:12:49[testRegex]: false
15:12:49[testRegex]: false
总结
本文介绍的正则表达式内容比较入门,但是对于java程序员处理常见问题已经够用,尤其是读懂已有代码,修修补补的能力应该是有的。