这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
文章目录
- Regex正则表达式
- 相关方法
- java.util.regex.Pattern 和 java.util.regex.Matcher
- 关于贪婪和非贪婪
- 定位符 ^ $ \b \B
- 正则表达式中的替换
- 转义是怎么回事
- 一些符号组合到底什么意思
Regex正则表达式
简介
正则表达式( regular expression )描述了一种字符串匹配的模式( pattern ),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
输入格式
正则表达式 | 匹配的字符串 |
---|---|
k | k |
abc | abc |
[abc] | […]表示字符集,是其中一个即可:a/b/c |
[abc][123] | a1/a2/a3/b1/b2/b3/c1/c2/c3 |
[a-z] | a/b/c/d…z |
[a-zA-Z0-9] | a/A/z/Z/0/9.. |
[^a-zA-Z] | ^表示排除一个范围:排除英文字母 |
[\u4e00-\u9fa5] | 中文范围 |
\d | 数字[0-9] |
\D | 排除数字[^0-9] |
\w | 单词字符和下划线[a-zA-Z_0-9] |
\W | 排除单词字符和下划线[^a-zA-Z_0-9] |
\s | 空白字符:回车/换行/制表符/…… |
\S | 排除空白字符 |
. | 任意字符 |
[abc]? | ?表示0个或1个 |
[abc]?[123] | 1/2/3/a1/a2… |
[abc]* | *表示0到多个 |
[abc]*[123] | 1/2/3/a1/aaabccba1/… |
[abc]+ | +表示1到多个 |
[abc]+[123] | a1/ab2/abcbcac1/… |
[abc]{3} | {}大括号表示固定数量:aaa/bca/cbc/… |
[abc]{3,5} | 3-5个:acc/abca/abcab/… |
[abc]{3,} | 3到多个:abc/abcccccaaaa/… |
| | 或,匹配左右其中一个表达式即可 |
举例说明:
限定符有 *
或 +
或 ?
或 {n}
或 {n,}
或 {n,m}
共6种。
zo+
,可以匹配 zo
、zooo
等,但不能匹配 zozo
,+ 号代表前面的字符必须至少出现一次(1次或多次)。如果想匹配 zozo
,需要把 zo 做为一个整体写正则表达式 (zo)+
出现在范围表达式之后,它应用于前边单个的范围表达式,例如匹配 1~99 的正整数表达式:[1-9][0-9]?
。[1-9] 设置第一个数字不是 0,[0-9]? 表示 0-9 不出现,或者出现一次。
相关方法
matches(正则表达式)
判断当前字符串,是否匹配正则表达式
栗子:匹配身份证号码
身份证号可以为15位(一代身份证)或者18位,18位的最后一位为校验码可为0-9或者x。
public class Test {
public static void main(String[] args) {
System.out.println("输入身份证号:");
String s = new Scanner(System.in).nextLine();
/*
* 123456789012345
* 123456789012345678
* 12345678901234567x
* 12345678901234567X
*
* \d{15}|\d{17}[\dxX]
* 需要对“\”进行转义
* \->\\
*/
String regex = "\\d{15}|\\d{17}[\\dxX]";
if(s.matches(regex)){
System.out.println("格式正确");
}else{
System.out.println("格式错误");
}
}
}
复制代码
运行结果:
栗子:匹配固定电话
这里仅为练习,假设固定电话的格式有以下几种
public class Test {
public static void main(String[] args) {
System.out.println("输入固定电话:");
String s = new Scanner(System.in).nextLine();
/*
* 123456
* 1234567
* 12345678
* (010)12345678
* (0102)12345678
* 010-123456
* 0102-1234567
*
* (\\d{3,4}-|\\(\\d{3,4}\\))?\\d{6,8}
*/
String regex = "(\\d{3,4}-|\\(\\d{3,4}\\))?\\d{6,8}";
if(s.matches(regex)){
System.out.println("格式正确");
}else{
System.out.println("格式错误");
}
}
}
复制代码
replaceAll(正则表达式,子串)
将找到的匹配子串,替换为新的子串
String regex = "store";
String s = "http://store.store.com";
System.out.println(s.replaceAll(regex, "www"));
System.out.println(s);
复制代码
运行结果,不会改变之前的字符串
http://www.www.com
http://store.store.com
复制代码
replace 和 replaceAll 可以达到一样的效果,他们的区别是:
replace:参数为 target 和 replacement,也就是替换的目标对象和新对象
replaceAll:参数为 regex 和 replacement,第二个参数都一样,第一个参数表示正则表达式,也就是说 replaceAll 可以支持正则表达替换。例如:
String str = "www.google.com";
System.out.print("匹配成功返回值 :" );
System.out.println(str.replaceAll("(.*)google(.*)", "baidu" ));
复制代码
运行结果:
匹配成功返回值 :baidu
replaceFirst:参数和 replaceAll一致,唯一不同的是它执行匹配第一个结果
split(正则表达式)
用匹配的子串,拆分字符串
public class Test {
public static void main(String[] args) {
System.out.println("输入关键词列表,用逗号、分号、空格分隔");
String s = new Scanner(System.in).nextLine();
String regex = "[,; ]+";
String[] a = s.split(regex);
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
复制代码
运行结果
java.util.regex.Pattern 和 java.util.regex.Matcher
Pattern 封装正则表达式
Matcher 封装正则表达式,和要匹配的字符串
创建实例
Pattern p = Pattern.compile(正则表达式);
Matcher m = p.matcher(要匹配的字符串);
find()
向后查找下一段匹配的子串。返回 boolean 值表示是否找到
find(int from)
从指定位置向后查找
group()
提取刚刚找到的子串
start()、end()
刚刚找到的子串的起始位置和结束位置
栗子:匹配字符串中的3到多个数字
System.out.println("输入:");
String s = new Scanner(System.in).nextLine();
//3到多个连续数字
String regex = "\\d{3,}";
Matcher m = Pattern.compile(regex)
.matcher(s);
//一直向后查找,直到false
while (m.find()) {
String s2 = m.group();
int start = m.start();
int end = m.end();
System.out.println(start + "-" + end + ":" + s2);
}
复制代码
输出结果
输入:
abcd1234efg56higk789
4-8:1234
17-20:789
关于贪婪和非贪婪
*
、+
限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个?
就可以实现非贪婪或最小匹配。
例如当使用<.*>
String s = "<H1>Chapter 1 - 介绍正则表达式</H1>";
String regex = "<.*>";
Matcher m = Pattern.compile(regex).matcher(s);
while (m.find()) {
String s2 = m.group();
System.out.println(s2);
}
复制代码
匹配的结果是
<H1>Chapter 1 - 介绍正则表达式</H1>
复制代码
如果用<.*?>
,匹配的结果是
<H1>
</H1>
复制代码
如果只想匹配开始的 H1 标签,表达式则是<\\w+?>
,匹配结果是
<H1>
复制代码
通过在 *
、+
或 ?
限定符之后放置 ?
,该表达式从”贪心”表达式转换为”非贪心”表达式或者最小匹配。
再举个例子可能更容易理解:
源字符串:aa<div>test1</div>bb<div>test2</div>cc
正则表达式一:<div>.*</div>
匹配结果一:<div>test1</div>bb<div>test2</div>
正则表达式二:<div>.*?</div>
匹配结果二:<div>test1</div>
、<div>test2</div>
仅从应用角度分析,可以这样认为,贪婪模式,就是在整个表达式匹配成功的前提下,尽可能多的匹配,也就是所谓的“贪婪”。非贪婪模式,就是在整个表达式匹配成功的前提下,尽可能少的匹配,也就是所谓的“非贪婪”。
定位符 ^ $ \b \B
定位符简单来说就是限定某些字符出现的位置。
正则表达式的定位符有
字符 | 描述 |
---|---|
^ | 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。 |
$ | 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。 |
\b | 匹配一个单词边界,即字与空格间的位置。 |
\B | 非单词边界匹配。 |
^
例如正则表达式^a
由于使用了^
定位符,因此字符串中必须以a开头。所以匹配上面正则表达式的字符有 abc、absolute 等以 a 开头的字符串,但是 back、123 等就不匹配了。
当^
后直接跟字符串时,表示以后面的整个字符串开始。当后面是一个表达式时,表示匹配以该表达式开始的字符串。下面举例。
^123[0-9]*[3]
: 查找以【123 + 任意多的0-9之间的字符 + 3】 开始的字符串。可匹配123999123
、1235673
等。
^[123][0-9]*[3]
:查找以【1或2或3】开始 + 0-9之间的数字任意多个数字 + 3 的字符串。可以匹配 29993
、303
等。
^(123).*[3]
:查找以【123】开始 + 任意多个任意字符 + 3 的字符串。可以匹配 123aabbcc3
、1233
等。
^a
和 [^a]
的区别
上尖号的使用有2种情况:(1)定位符;(2)[^…]元字符
很多初学者容易混淆上尖号,其实大家可以这样理解:上尖号,只有一种特殊情况,就是[^…]这种元字符的时候,上尖号才表示“非”,其他情况,上尖号都是表示定位符。
$
在正则表达式中,使用$
定位符来限定结尾位置的字符。
例如正则表达式a$
由于使用了$
定位符,因此字符串中必须以 a 结尾。所以匹配上面正则表达式的字符串有 panda、nana 等以 a 结尾的字符串,但是 abc、helicopter 等就不匹配了。
当$
前面直接跟字符时,表示匹配以前面整个字符串结束的字符串, 当前面是一个表达式时,表示匹配以该表达式结束的字符串。
[0-9]+123
: 匹 配 前 边 1 到 多 个 数 字 , 最 后 以 【 123 】 结 尾 的 字 符 串 , 如 ‘ 002244123 ‘ 等 。 ‘ [ 456 ] [ 123 ] :匹配前边 1 到多个数字,最后以 【123】 结尾的字符串,如`002244123`等。 `^[456][123] :匹配前边1到多个数字,最后以【123】结尾的字符串,如‘002244123‘等。‘[456][123]:查找以【4或5或6】开始,以【1或2或3】结束的字符串,如
43`等。
\b
\b
包含了字与空格间的位置
正则表达式er\\b
,匹配order to中的er,但不匹配 verb 中的 er。
\b
也包含了目标字符串的开始和结束位置。
正则表达式\\ba[a-z]{7}\\b
匹配以字母“a”开头的长度等于8的任意单词。因此\b
限定了单词的开头和结尾。
使用2个\b
来匹配一个单词,这是非常常用的方法。如果大家以后见到正则表达式中有2个\b
,也应该知道这是匹配单词的。
\b
字符的位置是非常重要的。如果它位于要匹配的字符串的开始,它在单词的开始处查找匹配项。如果它位于字符串的结尾,它在单词的结尾处查找匹配项例如:
正则表达式ter\\b
匹配单词 Chapter 中的字符串 ter,因为它出现在单词边界的前面。
\B
在正则表达式中,使用\B
定位符来限定一个非单词开始或结束时的字符。
正则表达式er\\B
,匹配 verb 中的 er,但不匹配 order 中的 er 。
正则表达式中的替换
替换练习
String str = "2013hello04world20";
//将数字替换成*
System.out.println(str.replaceAll("\\d", "*"));
//将连续数字换成*
System.out.println(str.replaceAll("\\d+", "*"));
//将手机后四位替换成*
str = "15200001111";
System.out.println(str.replaceAll("\\d{4}$", "****"));
//将手机中间四位替换成*
System.out.println(str.replaceAll("(\\d{3})(\\d{4})(\\d{3})", "$1****$3"));
//给链接地址增加a标签转换成超链接
str = "http://www.baidu.com,http://www.google.com";
System.out.println(str.replaceAll("(http://www\\..*?\\.com)", "<a href='https://juejin.cn/post/$1'>$1</a>"));
复制代码
运行结果
****hello**world**
*hello*world*
1520000****
152****1111
<a href='http://www.baidu.com'>http://www.baidu.com</a>,<a href='http://www.google.com'>http://www.google.com</a>
复制代码
需要说明的是,$1,$2等分别对应的是一个小括号
编辑器中使用正则替换
我们使用 Editplus 编辑一些文字,需要将 “我是程序员啊” 中的 程序员
替换成 工程师
,当然直接匹配可以,复杂一些可以使用正则
Ctrl+H,打开替换页面
其中 $1 和 $2 分别代表第 1 个和第 2 个括号内匹配到的内容,点击 replaceAll
替换指定内容到行尾
每次遇到 abc 将 abc 后边内容改为 def
数字替换
将连续的数字加上中括号
删除每一行行尾的指定字符
删除每行末尾的 345
替换带有半角括号的多行
用以下正则表达式
<script LANGUAGE=JavaScript1.1>\n<!--\nhtmlAdWH.'93163607','728','90'.;\n//-->\n</SCRIPT>\n
由于“(”、“)”被用做预设表达式(或者可以称作子表达式)的标志,所以可以把“(”、“)”使用任意字符标记替代,即半角句号:“.”
转义是怎么回事
需求:格式化金额。由于后台金额格式可以变化,所以由后台返回金额格式。app 端需替换服务器返回的格式化字符串中的”{0}”为金额。例如后台返回 ¥{0},我们的金额格式就为 ¥123
。后台返回 ${0},我们的金额格式就为 $123
String unformattedMoney = "12.00";
String s = "${0}";
String regex = "\\{0\\}";
s = s.replaceAll(regex, unformattedMoney);
System.out.println(s);
复制代码
执行结果
$12.00
复制代码
正则表达式中{
和}
这样的字符有特殊的意义,最开始的表格显示 [abc]{3}
中大括号表示固定数量,这个表达式可匹配:aaa/bca/cbc/…
而这里我们需要匹配大括号时,就需要转移写成 \{
。但是\
本身也是具有特殊意义的转义字符,所以\
就需要写成\\{
。需要先对\
进行一次转义。
所谓特殊字符,就是一些有特殊含义的字符,例如roo*t
中的*
,正常情况下这个正则表达式可以匹配 root、roooot、roooooot 等,*
号代表字符出现 0 到多次。但是如果要查找字符串中的*
符号,则需要对*
进行转义,即在其前加一个\
,ro\\**ot
匹配 ro****ot、root 等。
许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符”转义”,即,将反斜杠字符\
放在它们前面。下表列出了正则表达式中的特殊字符
特别字符 | 描述 |
---|---|
$ | 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 也匹配 ‘\n’ 或 ‘\r’。要匹配 字符本身,请使用 \$。 |
( ) | 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。 |
* | 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。 |
+ | 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。 |
. | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。 |
[ | 标记一个中括号表达式的开始。要匹配 [,请使用 \[。 |
? | 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。 |
\ | 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\’ 匹配 “\”,而 ‘\(‘ 则匹配 “(“。 |
^ | 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。 |
{ | 标记限定符表达式的开始。要匹配 {,请使用 \{。 |
| | 指明两项之间的一个选择。要匹配 |,请使用 \ |
一些符号组合到底什么意思
?:
?:
非获取匹配,匹配冒号后的内容,但不获取匹配结果,不进行存储供以后使用
如果想匹配 “program” 和 “project” 这两个单词,正则表达式可表示为 program|project
,也可表示为 pro(gram|ject)
。但用了()就表示会匹配括号里存在的内容且存储一份(看上面的替换的栗子就知道),用 | 隔开了,也就是说 gram 和 ject 都被存储了一份 但这样存储的内容是无意义的,所以表达式写成这样 pro(?:gram|ject),一是显得比较简洁,二是不会存储无意义的内容。
下面说明存储问题:
如果需要匹配连续重复的单词,如 lost lost
这里发现 lost 重复了,可用正则来 \\b(\\w+)\\b\\s+\\1\b
来找这样连续重复单词。
\b 匹配单词的开始
(\w+) 匹配单词并存储一份单词,当后面有反向引用时,则可以调用这个存储的单词
\b 匹配单词的结束
\s+一个或多个空格
\1这个是反向引用,引用前面括号里存储的单词 也就是 \w+
\b单词结束
这时如果把?:
加进去,这个表达式就无效了。因为 (?:\w) 这个单词虽可以被匹配但不会存储一份,后面出现的 \1 也不会调用前面括号里的单词,所以表达式就失效了。