Java正则速成秘籍(一) - 招式篇

导读

正则表达式是什么?有什么用?
正则表达式(Regular Expression)是一种文本规则,可以用来校验查找替换与规则匹配的文本。

又爱又恨的正则

正则表达式是一个强大的文本匹配工具,但是它的规则实在很繁琐,而且理解起来也颇为蛋疼,容易让人望而生畏。

如何学习正则

刚接触正则时,我看了一堆正则的语义说明,但是仍然不明所以。后来,我多接触一些正则的应用实例,渐渐有了感觉,再结合语义说明,终有领悟。我觉得正则表达式和武侠修练武功差不多,应该先练招式,再练心法。如果一开始就直接看正则的规则,保证你会懵逼。
当你熟悉基本招式(正则基本使用案例)后,也该修炼修炼心法(正则语法)了。真正的高手不能只靠死记硬背那么几招把式。就像张三丰教张无忌太极拳一样,领悟心法,融会贯通,少侠你就可以无招胜有招,成为传说中的绝世高手。

以上闲话可归纳为一句:学习正则应该从实例去理解规则。

欲练神功,必先自宫

打开秘籍:欲练神功,必先自宫!没有蛋,也就不会蛋疼了。

Java正则速成秘籍分三篇:

展示Java对于正则表达式的支持。

介绍正则表达式的语法规则。

  • 见招拆招篇

从实战出发,介绍正则的常用案例。

本文是Java正则速成秘籍的招式篇。主要介绍JDK对于正则表达式的支持。
心法篇和见招拆招篇会陆续推出。

概述

JDK中的java.util.regex包提供了对正则表达式的支持。

java.util.regex有三个核心类:

  • Pattern类:Pattern是一个正则表达式的编译表示。
  • Matcher类:Matcher是对输入字符串进行解释和匹配操作的引擎。
  • PatternSyntaxException:PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

注:需要格外注意一点,在Java中使用反斜杠"\"时必须写成 "\\"。所以本文的代码出现形如String regex = "\\$\\{.*?\\}" 其实就是"\$\{.*?\}",不要以为是画风不对哦。

Pattern类

Pattern类没有公共构造方法。要创建一个Pattern对象,你必须首先调用其静态方法compile,加载正则规则字符串,然后返回一个Pattern对象。

Pattern类一样,Matcher类也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象。

案例:Pattern和Matcher的初始化

Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);

Matcher类

Matcher类可以说是java.util.regex核心类中的必杀技!

Matcher类有三板斧(三类功能):

  • 校验
  • 查找
  • 替换

下面我们来领略一下这三块的功能。

校验文本是否与正则规则匹配

为了检查文本是否与正则规则匹配,Matcher提供了以下几个返回值为boolean的方法。

序号 方法及说明
1 **public boolean lookingAt() ** 尝试将从区域开头开始的输入序列与该模式匹配。
2 **public boolean find() **尝试查找与该模式匹配的输入序列的下一个子序列。
3 public boolean find(int start)重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
4 **public boolean matches() **尝试将整个区域与模式匹配。

如果你傻傻分不清上面的查找方法有什么区别,那么下面一个例子就可以让你秒懂。

案例:lookingAt vs find vs matches

public static void main(String[] args) {
 checkLookingAt("hello", "helloworld");
 checkLookingAt("world", "helloworld");

 checkFind("hello", "helloworld");
 checkFind("world", "helloworld");

 checkMatches("hello", "helloworld");
 checkMatches("world", "helloworld");
 checkMatches("helloworld", "helloworld");
}

private static void checkLookingAt(String regex, String content) {
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 if (m.lookingAt()) {
 System.out.println(content + "\tlookingAt: " + regex);
 } else {
 System.out.println(content + "\tnot lookingAt: " + regex);
 }
}

private static void checkFind(String regex, String content) {
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 if (m.find()) {
 System.out.println(content + "\tfind: " + regex);
 } else {
 System.out.println(content + "\tnot find: " + regex);
 }
}

private static void checkMatches(String regex, String content) {
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 if (m.matches()) {
 System.out.println(content + "\tmatches: " + regex);
 } else {
 System.out.println(content + "\tnot matches: " + regex);
 }
}

输出

helloworld lookingAt: hello
helloworld not lookingAt: world
helloworld find: hello
helloworld find: world
helloworld not matches: hello
helloworld not matches: world
helloworld matches: helloworld

说明

regex = “world” 表示的正则规则是以world开头的字符串,regex = “hello” 和regex = “helloworld” 也是同理。

  • lookingAt方法从头部开始,检查content字符串是否有子字符串于正则规则匹配。
  • find方法检查content字符串是否有子字符串于正则规则匹配,不管字符串所在位置。
  • matches方法检查content字符串整体是否与正则规则匹配。

查找匹配正则规则的文本位置

为了查找文本匹配正则规则的位置,Matcher提供了以下方法:

序号 方法及说明
1 **public int start() **返回以前匹配的初始索引。
2 public int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
3 public int end()返回最后匹配字符之后的偏移量。
4 public int end(int group)返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。
5 public String group()返回前一个符合匹配条件的子序列。
6 public String group(int group)返回指定的符合匹配条件的子序列。

案例:使用start()、end()、group() 查找所有匹配正则条件的子序列

public static void main(String[] args) {
 final String regex = "world";
 final String content = "helloworld helloworld";
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 System.out.println("content: " + content);

 int i = 0;
 while (m.find()) {
 i++;
 System.out.println("[" + i + "th] found");
 System.out.print("start: " + m.start() + ", ");
 System.out.print("end: " + m.end() + ", ");
 System.out.print("group: " + m.group() + "\n");
 }
}

输出

content: helloworld helloworld
[1th] found
start: 5, end: 10, group: world
[2th] found
start: 16, end: 21, group: world

说明

例子很直白,不言自明了吧。

替换匹配正则规则的文本

替换方法是替换输入字符串里文本的方法:

序号 方法及说明
1 public Matcher appendReplacement(StringBuffer sb, String replacement)实现非终端添加和替换步骤。
2 public StringBuffer appendTail(StringBuffer sb)实现终端添加和替换步骤。
3 **public String replaceAll(String replacement) ** 替换模式与给定替换字符串相匹配的输入序列的每个子序列。
4 public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列。
5 public static String quoteReplacement(String s)返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的appendReplacement 方法一个字面字符串一样工作。

案例:replaceFirst vs replaceAll

public static void main(String[] args) {
 String regex = "can";
 String replace = "can not";
 String content = "I can because I think I can.";

 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);

 System.out.println("content: " + content);
 System.out.println("replaceFirst: " + m.replaceFirst(replace));
 System.out.println("replaceAll: " + m.replaceAll(replace));
}

输出

content: I can because I think I can.
replaceFirst: I can not because I think I can.
replaceAll: I can not because I think I can not.

说明

replaceFirst:替换第一个匹配正则规则的子序列。

replaceAll:替换所有匹配正则规则的子序列。

案例:appendReplacement、appendTail和replaceAll

public static void main(String[] args) {
 String regex = "can";
 String replace = "can not";
 String content = "I can because I think I can.";
 StringBuffer sb = new StringBuffer();
 StringBuffer sb2 = new StringBuffer();

 System.out.println("content: " + content);
 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 while (m.find()) {
 m.appendReplacement(sb, replace);
 }
 System.out.println("appendReplacement: " + sb);
 m.appendTail(sb);
 System.out.println("appendTail: " + sb);
}

输出

content: I can because I think I can.
appendReplacement: I can not because I think I can not
appendTail: I can not because I think I can not.

说明

从输出结果可以看出,appendReplacementappendTail方法组合起来用,功能和replaceAll是一样的。

如果你查看replaceAll的源码,会发现其内部就是使用appendReplacementappendTail方法组合来实现的。

案例:quoteReplacement和replaceAll,解决特殊字符替换问题

public static void main(String[] args) {
 String regex = "\\$\\{.*?\\}";
 String replace = "${product}";
 String content = "product is ${productName}.";

 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 String replaceAll = m.replaceAll(replace);

 System.out.println("content: " + content);
 System.out.println("replaceAll: " + replaceAll);
}

输出

Exception in thread "main" java.lang.IllegalArgumentException: No group with name {product}
 at java.util.regex.Matcher.appendReplacement(Matcher.java:849)
 at java.util.regex.Matcher.replaceAll(Matcher.java:955)
 at org.zp.notes.javase.regex.RegexDemo.wrongMethod(RegexDemo.java:42)
 at org.zp.notes.javase.regex.RegexDemo.main(RegexDemo.java:18)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

说明

String regex = "\\$\\{.*?\\}";表示匹配类似${name}这样的字符串。由于${}都是特殊字符,需要用反义字符\来修饰才能被当做一个字符串字符来处理。

上面的例子是想将 ${productName} 替换为 ${product} ,然而replaceAll方法却将传入的字符串中的$当做特殊字符来处理了。结果产生异常。

如何解决这个问题?

JDK1.5引入了quoteReplacement方法。它可以用来转换特殊字符。其实源码非常简单,就是判断字符串中如果有\$,就为它加一个转义字符\

我们对上面的代码略作调整:

m.replaceAll(replace)改为m.replaceAll(Matcher.quoteReplacement(replace)),新代码如下:

public static void main(String[] args) {
 String regex = "\\$\\{.*?\\}";
 String replace = "${product}";
 String content = "product is ${productName}.";

 Pattern p = Pattern.compile(regex);
 Matcher m = p.matcher(content);
 String replaceAll = m.replaceAll(Matcher.quoteReplacement(replace));

 System.out.println("content: " + content);
 System.out.println("replaceAll: " + replaceAll);
}

输出

content: product is ${productName}.
replaceAll: product is ${product}.

说明

字符串中如果有\$,不能被正常解析的问题解决。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容