Java笔记 - 正则表达式 matches()与lookingAt()

本文主要内容:

1、正则表达式最基本的语法;

2、String内建的正则表达式功能,常用方法:split(), replaceFirst(), replaceAll();

3、使用Java创建正则表达式,相关类:Pattern, Matcher, Group;

4、Pattern标记的介绍,使用Pattern.complie(String regex, int flag)给正则表达式指定模式;

5、Pattern中的split方法,与String中的split类似;

6、Matcher的替换操作,常用方法:replaceAll(String replacement), replaceFirst(String replacement), appendReplacement(StringBuffer sb, String replacement) , appendTail(StringBuffer sb);

7、Matcher中reset()方法的使用;

8、在JAVA I/O中使用正则表达式。

很久以前,正则表达式就整合到了Unix工具集中,例如sed和awk,也整合到了Python和Perl之类的程序设计语言中。Java中的字符串操作还主要集中在String,StringBuffer和StringTokenizer类,提供的功能相比于正则表达式简单有限。

1、基本语法:

关于Java中的正则表达式详细的语法,可以参考JDK文档。

\\ 在其他语言中\\表示在正则表达式中插入一个普通的反斜线,而Java中则表示插入正则表达式的反斜线,所以其后的字符具有特殊的意义。 \\d 表示一位数字 \\\ 表示普通的反斜线 \n\t 表示换行和制表符,只需要使用单反斜线 -?\\d+ 表示可能有一个负号,后面跟着一位或多位数字

2、String内建的正则表达式功能:

String str = “-123”;
boolean isNum = str.matches(“(-|\+)?\\d+”); // 可能包含负号或者正号的整数
System.out.println(isNum); // output: true

2.1、String类自带的正则表达式工具:

2.1.1、split方法:

这是String中提供的一个非常有用的正则表达式工具,作用是将字符串从正则表达式匹配的地方分开。

// split方法演示
String content = “Hello, this is an iPad.”;
String[] items = content.split(“\\W+”); // 使用非单词字符分割字符串
System.out.println(Arrays.toString(items)); // [Hello, this, is, an, iPad] 可以发现逗号也被当做分隔符给去掉了

// split方法的重载版本,允许你限制字符串分割的次数
items = content.split(“\\W+”, 3);
System.out.println(Arrays.toString(items)); // [Hello, this, is an iPad.]

2.1.2、字符串替换之replaceFirst和replaceAll方法:

// 字符串的替换 replaceFirst和replaceAll方法
String str = “You make me cry, make me smile.”;
System.out.println(str.replaceFirst(“m\\w+”, “music”)); // You music me cry, make me smile.
System.out.println(str.replaceAll(“make|me”, “music”)); // You music music cry, music music smile.

3、创建正则表达式:

3.1、Pattern和Matcher:

为了更加方便的使用正则表达式,强烈建议多去浏览JDK文档中java.util.regex.Pattern那一页的内容。

下面是一个用Java创建正则表达式的例子:

String content = “one step tototoo far.”;
// 编译正则表达式生成一个Pattern对象
Pattern p = Pattern.compile(“(to){2,}”);
// 用Pattern对象的matcher()方法检索字符串,生成Matcher对象
Matcher m = p.matcher(content);
while(m.find()){
System.out.println(“Match \“” + m.group() + “\“ ad position “ + m.start() + “-“ + (m.end() - 1));
// output: Match “tototo” ad position 9-14
}

使用Matcher上的各种方法,可以判断各种不同类型的匹配是否成功:

boolean matches(): 尝试将整个区域与模式匹配。 如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。 返回: 当且仅当整个区域序列匹配此匹配器的模式时才返回 true。 boolean lookingAt(): 尝试将从区域开头开始的输入序列与该模式匹配。 与 matches) 方法类似,此方法始终从区域的开头开始;与之不同的是,它不需要匹配整个区域。 如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。 返回: 当且仅当输入序列的前缀匹配此匹配器的模式时才返回 true。 boolean find(): 尝试查找与该模式匹配的输入序列的下一个子序列。 此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始。 如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。 返回: 当且仅当输入序列的子序列匹配此匹配器的模式时才返回 true。 boolean find(int start): 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。 如果匹配成功,则可通过 start、end 和 group 方法获取更多信息,而 find()) 方法的后续调用将从此匹配操作未匹配的第一个字符开始。 返回: 当且仅当从给定索引开始的输入序列的子序列匹配此匹配器的模式时才返回 true。 抛出: IndexOutOfBoundsException- 如果开始点小于零或大于输入序列的长度。

下面是一个使用 boolean find(int start)的例子:

// boolean find(int start)
String content = “wings you are the hero~”;
Pattern p = Pattern.compile(“\\w+”);
Matcher m = p.matcher(content);
int i=0;
while(m.find(i)){
System.out.print(m.group() + “ “);
i++;
}

输出内容为:

wings ings ngs gs s you you ou u are are re e the the he e hero hero ero ro o

3.1.2、matches()与lookingAt()的使用

下面是使用matches()和lookingAt()的例子:

// 下面是Matcher的matches()和lookingAt()方法的使用举例
String content = “It’s my life, hero you are, no hero, wings you are the hero~”;
Pattern p = Pattern.compile(“\\w*o”);
Matcher m = p.matcher(content);
while(m.find()){
System.out.println(“find(): “ + m.group() + “ start at “ + m.start() + “ end at “ + (m.end() - 1));
}
if(m.lookingAt()){
System.out.println(“lookingAt(): “ + m.group() + “ start at “ + m.start() + “ end at “ + (m.end() - 1));
}
if(m.matches()){
System.out.println(“matches(): “ + m.group() + “ start at “ + m.start() + “ end at “ + (m.end() - 1));
}

输出结果为:

find(): hero start at 14 end at 17
find(): yo start at 19 end at 20
find(): no start at 28 end at 29
find(): hero start at 31 end at 34
find(): yo start at 43 end at 44
find(): hero start at 55 end at 58

可见matches()和lookingAt()方法并没有匹配到,这两个方法只有在正则表达式与输入的最开始处就开始匹配时才会成功,因为matches()只有在整个输入都匹配正则表达式时才会成功,而lookingAt()只要输入的第一部分匹配就会成功。

如果要使matches()匹配到,则正则表达式可以这样写:

Pattern p = Pattern.compile(“.*life.*“);

输出结果为:

matches(): It’s my life, hero you are, no hero, wings you are the hero~ start at 0 end at 59

如果要使lookingAt()方法匹配到,则正则表达式可以这样写即可(加上.*也可以):

Pattern p = Pattern.compile(“.*life”);

输出结果为:

lookingAt(): It’s my life start at 0 end at 11

3.2、Group:

组是用括号划分的正则表达式,使用组的编号来引用某个组。组号为0表示整个表达式,组号为1表示被第一个括号括起来的组,以此类推。举个例子:

(AB(C))D 0: ABCD 1:ABC 2:C

Matcher中和Group相关的方法:

public int groupCount(): public String group(): public String group(i): public int start(int group): public int end(int group):

下面是一个使用正则表达式组的例子:

String content = “It’s my life,\n wings you are the hero~”;
Pattern p = Pattern.compile(“(?m)(\\S+)\\s+(\\S+\\s+\\S+)$”);
// 上面的(?m)是模式标记(后面有介绍),相当于Pattern.MULTILINE,表示多行模式也可以这样写:
// Pattern p = Pattern.compile(“(\\S+)\\s+(\\S+\\s+\\S+)$”, Pattern.MULTILINE);
Matcher m = p.matcher(content);
if(m.find()){
for(int i=0; i<=m.groupCount(); i++){ // 注意这里的groupCount不包含匹配到的整个字符串
System.out.print(“[“ + m.group(i) + “]“);
// output: [It’s my life,][It’s][my life,]
}
}

注意:上面的(?m)是模式标记,表示多行模式,也可以使用Pattern.MULTILINE那种形式(后面会有介绍),输出为:

[It’s my life,][It’s][my life,]

如果没有多行模式,则输出为:

[are the hero~][are][the hero~]

3.3、Pattern标记:

前面我们有一个例子包含了(?m)是模式标记,表示多行模式。这种多行模式在Pattern类的compile()方法的另外一个版本中可以声明:

Pattern Pattern.compile(String regex, int flag);

其中flag来自以下的Pattern类中的常量:

CANON_EQ: 指定此标志后,当且仅当其完整规范分解匹配时,两个字符才可视为匹配。例如,当指定此标志时,表达式 “a\u030A” 将与字符串 “\u00E5” 匹配。默认情况下,匹配不考虑采用规范等价。
CASE_INSENSITIVE(?i): 默认情况下,不区分大小写的匹配假定仅匹配 US-ASCII 字符集中的字符。可以通过指定 UNICODE_CASE 标志连同此标志来启用 Unicode 感知的、不区分大小写的匹配。

COMMENTS(?x): 此模式将忽略空白和在结束行之前以 # 开头的嵌入式注释。

DOTALL(?s): 在 dotall 模式中,表达式 . 可以匹配任何字符,包括行结束符。默认情况下,此表达式不匹配行结束符。

MULTILINE(?m): 在多行模式中,表达式 ^ 和 $ 仅分别在行结束符前后匹配,或者在输入序列的结尾处匹配。默认情况下,这些表达式仅在整个输入序列的开头和结尾处匹配。

UNICODE_CASE(?u): 指定此标志后,由 CASE_INSENSITIVE 标志启用时,不区分大小写的匹配将以符合 Unicode Standard 的方式完成。默认情况下,不区分大小写的匹配假定仅匹配 US-ASCII 字符集中的字符。

UNIX_LINES(?d): 在此模式中,.、^ 和 $ 的行为中仅识别 ‘\n’ 行结束符。

在这些标记中Pattern.CASE_INSENSITIVE, Pattern.MULTILINE以及Pattern.COMMENTS(对声明或文档有用)特别有用。你还可以通过“或”(|)操作符组合多个标记的功能:

Pattern p = Pattern.compile(“java”, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher(“java is a programming language\n JAVA”);
while(m.find()){
System.out.println(m.group());
}

输出为:

java
JAVA

3.4、split()方法:

与String的split()方法类似,只不过使用格式反过来了:

String content = “Wings- you are -the -hero~”;
System.out.println(Arrays.toString(Pattern.compile(“-“).split(content)));
System.out.println(Arrays.toString(Pattern.compile(“-“).split(content, 2)));

输出为:

[Wings, you are , the , hero~]
[Wings, you are -the -hero~]

3.5、替换操作:

主要看到Matcher的以下几个方法:

replaceAll

public String replaceAll(String replacement)

替换模式与给定替换字符串相匹配的输入序列的每个子序列。

replaceFirst

public String replaceFirst(String replacement)

替换模式与给定替换字符串匹配的输入序列的第一个子序列。

appendReplacement

public Matcher appendReplacement(StringBuffer sb,
String replacement)

实现非终端添加和替换步骤。此方法执行以下操作:

  1. 它从添加位置开始在输入序列读取字符,并将其添加到给定字符串缓冲区。在读取以前匹配之前的最后字符(即位于索引 start() - 1 处的字符)之后,它就会停止。
  2. 它将给定替换字符串添加到字符串缓冲区。
  3. 它将此匹配器的添加位置设置为最后匹配位置的索引加 1,即 end()

appendTail

public StringBuffer appendTail(StringBuffer sb)

实现终端添加和替换步骤。此方法从添加位置开始从输入序列读取字符,并将其添加到给定字符串缓冲区。可以在一次或多次调用 appendReplacement 方法后调用它来复制剩余的输入序列。

下面通过一个程序来演示一下这几个方法的使用,并且通过这个程序,更加熟悉正则表达式的使用:

String content = “/! long long ago, the is a man called Jack, \n” +
“ he has one boat. !
/“;
// Pattern.DOTALL: 这种模式下 . 可以匹配任何字符,包括换行符
Pattern p = Pattern.compile(“/\*!(.)!\\/“, Pattern.DOTALL);
Matcher m = p.matcher(content);
if(m.find())
// 匹配到/! !/中的内容
content = m.group(1);
// 把两个以上空格的地方缩减为一个空格
content = content.replaceAll(“ {2,}”, “ “);
// 开启多行模式,删除每一行开头部分的空格,+表示匹配一个或多个
content = content.replaceAll(“(?m)^ +”, “”);
// 匹配到字符串中的第一个元音字母,并替换为VOWEL
content = content.replaceFirst(“[aeiou]“, “VOWEL”);
// 下面一段程序演示把字符串中的所有元音字母替换为大写
Pattern p1 = Pattern.compile(“[aeiou]“);
Matcher m1 = p1.matcher(content);
StringBuffer sb = new StringBuffer();
while(m1.find()){
// 非终端添加和替换,
m1.appendReplacement(sb, m1.group().toUpperCase());
}
// 终端添加和替换
m1.appendTail(sb);
System.out.println(sb);

输出结果为:

lVOWELng lOng AgO, thE Is A mAn cAllEd JAck,
hE hAs OnE bOAt.

注意,上面的两个替换操作都只使用了一次replaceAll(),所以,与其编译为Pattern,不如直接使用String的replaceAll()方法,而且开销也比较小。

3.6、reset()方法:

如果要讲一个现有的Matcher对象运用于一个新的字符序列,可以使用reset()方法,如果使用不带参数的reset()方法,则可以将Matcher对象重新设置到当前字符序列的起始位置:

Matcher m = Pattern.compile(“[hks][io][sbo]“)
.matcher(“This is the key.”);
while(m.find())
System.out.println(m.group()); // output: his
// 把Matcher应用于一个新的字符序列
m.reset(“New version”);
while(m.find())
System.out.println(m.group()); // output: sio

4、正则表达式与Java I/O

下面通过一个字符串的ArrayList模拟文本的逐行读取。这里在循环外创建了一个空的Matcher对象,然后用reset()方法每次为Matcher加载一次输入,这样处理会有一定 性能优化:

ArrayList list = new ArrayList();
list.add(“This is String…”);
list.add(“Big blue ocean”);
list.add(“Search something”);
Pattern p = Pattern.compile(“\\b[Ssb]\\w+”, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(“”);
for(String line : list){
m.reset(line);
while(m.find()){
System.out.println(m.group() + “: start at “ + m.start());
}
}

输出结果为:

String: start at 8
Big: start at 0
blue: start at 4
Search: start at 0
something: start at 7

arthinking wechat
欢迎关注itzhai公众号