博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java正则系列: (1)入门教程
阅读量:6786 次
发布时间:2019-06-26

本文共 17120 字,大约阅读时间需要 57 分钟。

本文简要介绍Java的正则表达式及其实现方式,并通过实例讲解正则表达式的具体用法。

1. 正则表达式

1.1. 简介

正则表达式(Regular Expression), 简称 正则, 也翻译为 正规式, 用来表示文本搜索模式。英文缩写是 regex(reg-ex).

搜索模式(search pattern)可能多种多样, 如, 单个字符(character), 特定字符串(fixed string), 包含特殊含义的复杂表达式等等. 对于给定的字符串, 正则表达式可能匹配一到多次, 也可能一次都不匹配。

正则表达式一般用来查找、编辑和替换文本(text), 本质上, text(文本) 和 string(字符串) 是一回事。

用正则表达式来分析/修改文本的过程, 称为: 应用于文本/字符串的正则表达式 。正则表达式扫描字符串的顺序是从左到右. 每个字符都只能被匹配成功一次, 下次匹配扫描就会从后面开始。例如, 正则表达式 aba, 匹配字符串 ababababa 时, 只会扫描到两个匹配(aba_aba__)。

1.2. 示例

最简单的例子是字母串。例如, 正则表达式 Hello World 能匹配的就是字符串 “Hello World”。 正则表达式中, 点号 .(dot,英文句号)属于通配符, 点号匹配任意一个字符(character); 例如, “a” 或者 “1”; 当然, 默认情况下点号不能匹配换行 \n, 需要特殊标识指定才行。

下表列举了一些简单的正则表达式,和对应的匹配模式。

正则表达式 Matches
this is text 完全匹配 “this is text”
this\s+is\s+text 匹配的内容为: “this”, 加上1到多个空白符(whitespace character, 如空格,tab,换行等), 加上 “is”, 加上1到多个空白符, 再加上 “text”.
^\d+(\.\d+)? 正则表达式以转义字符 ^(小尖号)打头, 表示这行必须以小尖号后面的字符模式开始, 才会达成匹配. \d+ 匹配1到多个数字. 英文问号 ? 表示可以出现 0~1次. \. 匹配的是字符 “.”, 小括号(parentheses) 表示一个分组. 所以这个正则表达式可以匹配正整数或者小数,如: “5”, “66.6” 或者 “5.21” 等等.

说明,中文的全角空格( )字符不属于空白字符(whitespace characters), 可以认为其属于一个特殊的汉字。

1.3. 编程语言对正则表达式的支持

大多数编程语言都支持正则表达式, 如 Java、Perl, Groovy 等等。但各种语言的正则表达式写法略有一些不同。

2. 预备知识

本教程要求读者具备Java语言相关的基础知识。

下面的一些示例通过 JUnit 来验证执行结果。如果不想使用JUnit, 也可以改写相关代码。关于JUnit的知识请参考 。

3. 语法规则

本章介绍各种正则元素的范本, 我们会先介绍什么是元字符(meta character)。

3.1. 通用表达式简介

正则表达式 说明
. 点号(.), 匹配任意一个字符
^regex 小尖号(^), 起始标识, 前面不能出现其他字符.
regex$ 美元符号($,dollar,美刀), 结束标识,后面不能再出现其他字符.
[abc] 字符组(set), 匹配 a 或 b 或 c.
[abc][vz] 字符组(set), 匹配 a 或 b 或 c,紧接着是 v 或 z.
[^abc] 如果小尖号(^, caret, 此处读作 ) 出现在中括号里面的第一位, 则表示否定(negate). 这里匹配: 除 a, b, c 之外的其他任意字符.
[a-d1-7] 范围表示法: 匹配 ad 之间的单个字符, 或者 17之间的单个字符, 整体只匹配单个字符, 而不是 d1 这种组合.
X|Z 匹配 X 或者 Z.
XZ 匹配XZ, X和Z必须按顺序全部出现.
$ 判断一行是否结束.

3.2. 元字符

下面这些是预定义的元字符(Meta characters), 可用于提取通用模式, 如 \d 可以代替 [0-9], 或者[0123456789]

正则表达式 说明
\d 单个数字, 等价于 [0-9] 但更简洁
\D 非数字, 等价于 [^0-9] 但更简洁
\s 空白字符(whitespace), 等价于 [ \t\n\x0b\r\f]
\S 非空白字符, 等价于 [^\s]
\w 反斜线加上小写w, 表示单个标识符,即字母数字下划线, 等价于 [a-zA-Z_0-9]
\W 非单词字符, 等价于 [^\w]
\S+ 匹配1到多个非空白字符
\b 匹配单词外边界(word boundary), 单词字符指的是 [a-zA-Z0-9_]

这些元字符主要取自于对应单词的英文首字母, 例如: digit(数字), space(空白), word (单词), 以及 boundary(边界)。对应的大写字符则用来表示取反。

3.3. 量词

量词(Quantifier)用来指定某个元素可以出现的次数。?, *, +{} 等符号定义了正则表达式的数量。

正则表达式 说明 示例
* 0到多次, 等价于 {0,} X* 匹配0到多个连续的X, .* 则匹配任意字符串
+ 1到多次, 等价于 {1,} X+ 匹配1到多个连续的X
? 0到1次, 等价于 {0,1} X? 匹配0个,后者1个X
{n} 精确匹配 n 次 {} 前面序列出现的次数 \d{3} 匹配3位数字, .{10} 匹配任意10个字符.
{m, n} 出现 m 到 n 次, \d{1,4} 匹配至少1位数字,至多4位数字.
*? 在量词后面加上 ?, 表示懒惰模式(reluctant quantifier). 从左到右慢慢扫描, 找到第一个满足正则表达式的地方就暂停搜索, 用来尝试匹配最少的字符串.

3.4. 分组和引用

可以对正则表达式进行分组(Grouping), 用圆括号 () 括起来。这样就可以对括号内的整体使用量词。

当然, 在进行替换的时候, 还可以对分组进行引用。也就是捕获组(captures the group)。向后引用(back reference) 指向匹配中该分组所对应的字符串。进行替换时可以通过 $ 来引用。

使用 $ 来引用一个捕获组。例如 $1 表示第一组, $2 表示第二组, 以此类推, $0则表示整个正则所匹配的部分。

例如, 想要去掉单词后面, 句号/逗号(point or comma)前面的空格。可以把句号/逗号写入正则中, 然后原样输出到结果中即可。

// 去除单词与 `.|,` 之间的空格String pattern = "(\\w)(\\s+)([\\.,])";System.out.println(EXAMPLE_TEST.replaceAll(pattern, "$1$3"));

提取 标签的内容:

// 提取  标签的内容pattern = "(?i)(
)(.+?)()";String updated = EXAMPLE_TEST.replaceAll(pattern, "$2");

3.5. 环视

环视(lookaround), 分为顺序环视(Lookahead)与逆序环视(lookbehind), 属于零宽度断言(zero-length assertion)。 类似于行起始标识(^)和结束标识($); 或者单词边界(\b)一类的位置标识。

顺序否定环视(Negative look ahead), 用于在匹配的同时, 排除掉某些情形。也就是说其后面不能是符合某种特征的字符串。

顺序否定环视(Negative look ahead) 使用 (?!pattern) 这种格式定义。例如, 下面的正则, 只匹配后面不是 b 字母的 “a” 字母。

a(?!b)

类似的, 顺序环视(look ahead), 也叫顺序肯定环视。 如,只匹配a字母, 但要求后面只能是 b 字母, 否则这个 a 就不符合需要:

a(?=b)

注意,环视 是一种向前/后查找的语法: (?=exp), 会查找后面位置的 exp; 所环视的内容却不包含在正则表达式匹配中。

环视(lookaround)是一种高级技巧, 环视的部分不会匹配到结果之中, 但却要求匹配的字符串前面/后面具备环视部分的特征。

如果将等号换成感叹号, 就是环视否定 (?!exp), 变成否定语义,也就是说查找的位置的后面不能是exp。

逆序肯定环视, (?<=exp), 表示所在位置左侧能够匹配 exp

逆序否定环视, (?<!exp), 表示所在位置左侧不能匹配 exp

详情请参考: 正则应用之——逆序环视探索:

参考: 利用正则表达式排除特定字符串

3.6. 正则表达式的模式

在正则表达式开头可以指定模式修饰符(mode modifier)。还可以组合多种模式, 如 (?is)pattern

  • (?i) 正则表达式匹配时不区分大小写。

  • (?s) 单行模式(single line mode), 使点号(.) 匹配所有字符, 包括换行(\n)。

  • (?m) 多行模式(multi-line mode), 使 小尖号(^,caret) 和 美元符号($, dollar) 匹配目标字符串中每一行的开始和结束。

3.7. Java中的反斜杠

在Java字符串中, 反斜杠(\, backslash) 是转义字符, 有内置的含义。在源代码级别, 需要使用两个反斜杠\\来表示一个反斜杠字符。如果想定义的正则表达式是 \w, 在 .java 文件源码中就需要写成 \\w。 如果想要匹配文本中的1个反斜杠, 则源码中需要写4个反斜杠 \\\\

4. String类正则相关的方法

4.1. String 类重新定义了正则相关的方法

Java中 String 类内置了4个支持正则的方法, 即: matches(), split(), replaceFirst()replaceAll() 方法。 需要注意, replace() 是纯字符串替换, 不支持正则表达式。

这些方法并没有对性能进行优化。稍后我们将讨论优化过的类。

方法 说明
s.matches("regex") 判断字符串 s 是否能匹配正则 "regex". 只有整个字符串匹配正则才返回 true .
s.split("regex") 用正则表达式 "regex" 作为分隔符来拆分字符串, 返回结果是 String[] 数组. 注意 "regex" 对应的分隔符并不包含在返回结果中.
s.replaceFirst("regex", "replacement") 替换第一个匹配 "regex" 的内容为 "replacement.
s.replaceAll("regex", "replacement") 将所有匹配 "regex" 的内容替换为 "replacement.

下面是对应的示例。

package de.vogella.regex.test;public class RegexTestStrings {        public static final String EXAMPLE_TEST = "This is my small example "                        + "string which I'm going to " + "use for pattern matching.";        public static void main(String[] args) {                System.out.println(EXAMPLE_TEST.matches("\\w.*"));                String[] splitString = (EXAMPLE_TEST.split("\\s+"));                System.out.println(splitString.length);// should be 14                for (String string : splitString) {                        System.out.println(string);                }                // 将所有空白符(whitespace) 替换为 tab                System.out.println(EXAMPLE_TEST.replaceAll("\\s+", "\t"));        }}

4.2. 示例

下面给出一些正则表达式的使用示例。请参照注释信息。

If you want to test these examples, create for the Java project de.vogella.regex.string.

如果想测试这些示例, 请将java文件放到一个Java包下, 如 de.vogella.regex.string

package de.vogella.regex.string;public class StringMatcher {
// 如果字符串完全匹配 "`true`", 则返回 true public boolean isTrue(String s){ return s.matches("true"); } // 如果字符串完全匹配 "`true`" 或 "`True`", 则返回 true public boolean isTrueVersion2(String s){ return s.matches("[tT]rue"); } // 如果字符串完全匹配 "`true`" 或 "`True`" // 或 "`yes`" 或 "`Yes`", 则返回 true public boolean isTrueOrYes(String s){ return s.matches("[tT]rue|[yY]es"); } // 如果包含字符串 "`true`", 则返回 true public boolean containsTrue(String s){ return s.matches(".*true.*"); } // 如果包含3个字母, 则返回 true public boolean isThreeLetters(String s){ return s.matches("[a-zA-Z]{3}"); // 当然也等价于下面这种比较土的方式 // return s.matches("[a-Z][a-Z][a-Z]"); } // 如果不以数字开头, 则返回 true public boolean isNoNumberAtBeginning(String s){ // 可能 "^\\D.*" 更好一点 return s.matches("^[^\\d].*"); } // 如果包含了 `b` 之外的字符, 则返回 true public boolean isIntersection(String s){ return s.matches("([\\w&&[^b]])*"); } // 如果包含的某串数字小于300, 则返回 true public boolean isLessThenThreeHundred(String s){ return s.matches("[^0-9]*[12]?[0-9]{1,2}[^0-9]*"); }}

And a small JUnit Test to validates the examples.

我们通过 JUnit 测试来验证。

package de.vogella.regex.string;import org.junit.Before;import org.junit.Test;import static org.junit.Assert.assertFalse;import static org.junit.Assert.assertTrue;public class StringMatcherTest {
private StringMatcher m; @Before public void setup(){ m = new StringMatcher(); } @Test public void testIsTrue() { assertTrue(m.isTrue("true")); assertFalse(m.isTrue("true2")); assertFalse(m.isTrue("True")); } @Test public void testIsTrueVersion2() { assertTrue(m.isTrueVersion2("true")); assertFalse(m.isTrueVersion2("true2")); assertTrue(m.isTrueVersion2("True"));; } @Test public void testIsTrueOrYes() { assertTrue(m.isTrueOrYes("true")); assertTrue(m.isTrueOrYes("yes")); assertTrue(m.isTrueOrYes("Yes")); assertFalse(m.isTrueOrYes("no")); } @Test public void testContainsTrue() { assertTrue(m.containsTrue("thetruewithin")); } @Test public void testIsThreeLetters() { assertTrue(m.isThreeLetters("abc")); assertFalse(m.isThreeLetters("abcd")); } @Test public void testisNoNumberAtBeginning() { assertTrue(m.isNoNumberAtBeginning("abc")); assertFalse(m.isNoNumberAtBeginning("1abcd")); assertTrue(m.isNoNumberAtBeginning("a1bcd")); assertTrue(m.isNoNumberAtBeginning("asdfdsf")); } @Test public void testisIntersection() { assertTrue(m.isIntersection("1")); assertFalse(m.isIntersection("abcksdfkdskfsdfdsf")); assertTrue(m.isIntersection("skdskfjsmcnxmvjwque484242")); } @Test public void testLessThenThreeHundred() { assertTrue(m.isLessThenThreeHundred("288")); assertFalse(m.isLessThenThreeHundred("3288")); assertFalse(m.isLessThenThreeHundred("328 8")); assertTrue(m.isLessThenThreeHundred("1")); assertTrue(m.isLessThenThreeHundred("99")); assertFalse(m.isLessThenThreeHundred("300")); }}

5. Pattern与Matcher简介

For advanced regular expressions the java.util.regex.Pattern and java.util.regex.Matcher classes are used.

要支持正则表达式的高级特性, 需要借助 java.util.regex.Patternjava.util.regex.Matcher 类。

首先创建/编译 Pattern 对象, 用来定义正则表达式。对 Pattern 对象, 给定一个字符串, 则产生一个对应的 Matcher 对象。通过 Matcher 对象就可以对 String 进行各种正则相关的操作。

package de.vogella.regex.test;import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexTestPatternMatcher {        public static final String EXAMPLE_TEST = "This is my small example string which I'm going to use for pattern matching.";        public static void main(String[] args) {                Pattern pattern = Pattern.compile("\\w+");                // 如需忽略大小写, 可以使用:                // Pattern pattern = Pattern.compile("\\w+", Pattern.CASE_INSENSITIVE);                Matcher matcher = pattern.matcher(EXAMPLE_TEST);                // 查找所有匹配的结果                while (matcher.find()) {                        System.out.print("Start index: " + matcher.start());                        System.out.print(" End index: " + matcher.end() + " ");                        System.out.println(matcher.group());                }                // 将空格替换为 tabs                Pattern replace = Pattern.compile("\\s+");                Matcher matcher2 = replace.matcher(EXAMPLE_TEST);                System.out.println(matcher2.replaceAll("\t"));        }}

6. 正则表达式示例

下面列出了常用的正则表达式使用情景。希望读者根据实际情况进行适当的调整。

6.1 或(Or)

任务: 编写正则表达式, 用来匹配包含单词 “Joe” 或者 “Jim” , 或者两者都包含的行。

创建 de.vogella.regex.eitheror 包和下面的类。

package de.vogella.regex.eitheror;import org.junit.Test;import static org.junit.Assert.assertFalse;import static org.junit.Assert.assertTrue;public class EitherOrCheck {
@Test public void testSimpleTrue() { String s = "humbapumpa jim"; assertTrue(s.matches(".*(jim|joe).*")); s = "humbapumpa jom"; assertFalse(s.matches(".*(jim|joe).*")); s = "humbaPumpa joe"; assertTrue(s.matches(".*(jim|joe).*")); s = "humbapumpa joe jim"; assertTrue(s.matches(".*(jim|joe).*")); }}

6.2. 匹配电话号码

任务: 编写正则表达式, 匹配各种电话号码。

假设电话号码(Phone number)的格式为 “7位连续的数字”; 或者是 “3位数字加空格/横线, 再加上4位数字”。

package de.vogella.regex.phonenumber;import org.junit.Test;import static org.junit.Assert.assertFalse;import static org.junit.Assert.assertTrue;public class CheckPhone {
@Test public void testSimpleTrue() { String pattern = "\\d\\d\\d([,\\s])?\\d\\d\\d\\d"; String s= "1233323322"; assertFalse(s.matches(pattern)); s = "1233323"; assertTrue(s.matches(pattern)); s = "123 3323"; assertTrue(s.matches(pattern)); }}

6.3. 判断特定数字范围

以下示例用来判断文本中是否具有连续的3位数字。

创建 de.vogella.regex.numbermatch 包和下面的类。

package de.vogella.regex.numbermatch;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.junit.Test;import static org.junit.Assert.assertFalse;import static org.junit.Assert.assertTrue;public class CheckNumber {
@Test public void testSimpleTrue() { String s= "1233"; assertTrue(test(s)); s= "0"; assertFalse(test(s)); s = "29 Kasdkf 2300 Kdsdf"; assertTrue(test(s)); s = "99900234"; assertTrue(test(s)); } public static boolean test (String s){ Pattern pattern = Pattern.compile("\\d{3}"); Matcher matcher = pattern.matcher(s); if (matcher.find()){ return true; } return false; }}

6.4. 校验超链接

假设需要从网页中找出所有的有效链接。当然,需要排除 “javascript:” 和 “mailto:” 开头的情况。

创建 de.vogella.regex.weblinks 包, 以及下面的类:

package de.vogella.regex.weblinks;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.MalformedURLException;import java.net.URL;import java.util.ArrayList;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern;public class LinkGetter {        private Pattern htmltag;        private Pattern link;        public LinkGetter() {                htmltag = Pattern.compile("
]*href=\"[^>]*>(.*?)"); link = Pattern.compile("href=\"[^>]*\">"); } public List
getLinks(String url) { List
links = new ArrayList
(); try { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(new URL(url).openStream())); String s; StringBuilder builder = new StringBuilder(); while ((s = bufferedReader.readLine()) != null) { builder.append(s); } Matcher tagmatch = htmltag.matcher(builder.toString()); while (tagmatch.find()) { Matcher matcher = link.matcher(tagmatch.group()); matcher.find(); String link = matcher.group().replaceFirst("href=\"", "") .replaceFirst("\">", "") .replaceFirst("\"[\\s]?target=\"[a-zA-Z_0-9]*", ""); if (valid(link)) { links.add(makeAbsolute(url, link)); } } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return links; } private boolean valid(String s) { if (s.matches("javascript:.*|mailto:.*")) { return false; } return true; } private String makeAbsolute(String url, String link) { if (link.matches("http://.*")) { return link; } if (link.matches("/.*") && url.matches(".*$[^/]")) { return url + "/" + link; } if (link.matches("[^/].*") && url.matches(".*[^/]")) { return url + "/" + link; } if (link.matches("/.*") && url.matches(".*[/]")) { return url + link; } if (link.matches("/.*") && url.matches(".*[^/]")) { return url + link; } throw new RuntimeException("Cannot make the link absolute. Url: " + url + " Link " + link); }}

6.5. 查找重复的单词

下面的正则表达式用来匹配重复的单词。

\b(\w+)\s+\1\b

\b 是单词边界, \1 则引用第一个分组, 此处的第一个分组为前一个单词 (\w+)

(?!-in)\b(\w+) \1\b 通过环视否定, 来匹配前面不是 “-in” 开始的重复单词。

提示: 可以在最前面加上 (?s) 标志来执行跨行搜索。

6.6. 查找每行起始位置的元素

下面的正则, 用来查找一行开头的单词 “title”, 前面允许有空格。

(\n\s*)title

6.7. 找到非Javadoc风格的语句

有时候, 在Java代码中会出现非Javadoc风格(Non-Javadoc)的语句; 如 Java 1.6 中的 @Override 注解, 用于告诉IDE该方法覆写了超类方法。这种是可以从源码中清除的。下面的正则用来找出这类注解。

(?s) /\* \(non-Javadoc\).*?\*/

6.7.1. 用 Asciidoc 替换 DocBook 声明

例如有下面这样的XML:

可以用下面的正则来匹配:

`\s+
\R.\s+

替换目标可以是下面这样的regex:

`\R[source,java]\R----\R include::res/$1[]\R----

7. 在Eclipse中使用正则表达式

在Eclipse或者其他编辑器中, 可以使用正则来执行查找和替换。一般使用快捷键 Ctrl+H 打开 搜索/Search 对话框。

选择 File Search 选项卡, 并勾选 Regular expression 标识, 则可以进行正则查找/替换。当然, 还可以指定文件类型, 以及查找/替换的目录范围。

下图展示了如何查找XML标签 <![CDATA[]]]> 和前面的空格, 以及如何去除这些空格。

Search and replace in Eclipse part 2

在结果对话框中可以查看有哪些地方会被替换, 可以去掉不想替换的元素。没问题的话, 点击 OK 按钮, 就会进行替换。

Search and replace in Eclipse part 3

8. 相关链接

  • 示例代码下载:

原文链接:

原文日期: 2016.06.24

翻译日期: 2017-12-28

翻译人员:

你可能感兴趣的文章
Kubernetes的service mesh——第一部分:Service的重要指标
查看>>
全链路监控
查看>>
我的友情链接
查看>>
我的IT博客之路
查看>>
深入理解javascript原型和闭包(10)——this
查看>>
系统集成资质培训-论文写作-几个题目如何写?(updated)
查看>>
搭建自己的框架之1:Rxjava2+Retrofit2 实现Android Http请求
查看>>
排序算法-快速排序
查看>>
CSS3 Background 属性介绍
查看>>
frameset 的一些小应用
查看>>
eclipse自动换行
查看>>
Android PDF 阅读器源码
查看>>
我的友情链接
查看>>
silverlight渐隐效果
查看>>
使用Docker实现php代码在线测试执行工具-toolfk.com
查看>>
簡單範例 mergecap,wireshark 付屬程式
查看>>
网络文件传输学习
查看>>
Installation Oracle11gR2 RAC One Node ---创建数据库
查看>>
spring 通过EsClientFactory注入elasticsearch
查看>>
打造中国第一品牌安全网关
查看>>