欢迎并且感谢大家指出我的问题,由于本人水平有限,有些内容写的不是很全面,只是把比较实用的东西给写下来,还有对一些常用的正则表达式进行收集整理,如果有写的不对的地方,还希望大家多多指教!谢谢大家!🥰
在我第一次接触到正则表达式的时候我就很纳闷,到底谁是谁研究出这么多符号的组合,就那么一个字符规则校验就足以使那么多程序员为之抓狂。我查阅了很多资料和参考文献,将正则表达式的很多知识总结到了一起,废话不多说直接开始。
什么是正则表达式?
正则表达式是一组由字母和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。通俗的讲就是校验字符串是否满足规则或者在文本查找满足要求的内容。我在这里给大家举个简单的例子。
如果我需要对一组qq号(字符串)进行检验,例如,要求qq号必须是6位到20位之内的,不能以0开头,还必须全是数字。按照正常来说,我需要写一个比较复杂的检测方法。像下面这个是不是看起来比较麻烦。
java">public static boolean checkQQ(String qq) {//规则:6位及20位之内,0不能在开头,必须全部是数字int len = qq.length();if (len < 6 || len > 20) {return false;}//0不能在开头if (qq.startsWith("0")) {return false;}//必须全部是数字for (int i = 0; i < qq.length(); i++) {char c = qq.charAt(i);if (c < '0' || c > '9') {return false;}}return true;
}
但是如果我们要是使用正则表达式的话只需要一行代码即可,看起来是不是超级哇塞,超级简洁。
java">qq.matches("[1 - 9]\\d{5,19}");
在Java中可以使用matches方法:String类的matches方法用于尝试将整个字符串与给定的正则表达式进行匹配,如果整个字符串匹配该正则表达式则返回true,否则返回false。
元字符
元字符:即为有特定含义的字符,常见的元字符如下
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始(在集合字符里[^a]表示非(不匹配)的意思 |
$ | 匹配字符串的结束 |
1. 点号(.
)
点号是一个非常常用的元字符,它可以匹配除换行符之外的任意单个字符。例如,正则表达式 a.c
可以匹配 "abc"、"a1c"、"a@c" 等字符串。这是因为点号在这里代表了任意一个非换行的字符,只要满足 "a" 开头,"c" 结尾,中间是任意单个字符的模式,都能被匹配。需要注意的是,不同的编程语言或工具可能对换行符的定义有所不同,有些可能会将回车符(\r
)、换行符(\n
)或它们的组合都视为换行情况。
2. 星号(*
)
星号用于表示匹配前面的元素零次或多次。例如,正则表达式 ab*c
可以匹配多种情况。当 b
出现零次时,它可以匹配 "ac";当 b
出现一次时,能匹配 "abc";当 b
出现多次时,像 "abbbc" 也能被成功匹配。这是因为星号允许前面的 b
字符出现任意次数,包括零次。
3. 加号(+
)
加号与星号类似,但它要求前面的元素至少出现一次。以正则表达式 ab+c
为例,它可以匹配 "abc"、"abbbc" 等字符串,但不能匹配 "ac",因为 b
至少要出现一次才能满足该正则表达式的模式。
4. 问号(?
)
问号表示匹配前面的元素零次或一次。例如,正则表达式 ab?c
可以匹配 "ac"(此时 b
出现零次)和 "abc"(b
出现一次)。问号常用于表示某个字符或一组字符是可选的情况。
5. 花括号({n}
、{n,}
、{n,m}
)
花括号用于精确控制前面元素的匹配次数,有三种常见的用法:
{n}
:精确匹配前面的元素n
次。例如,a{3}
只能匹配 "aaa",如果文本中是 "aa" 或 "aaaa" 则无法匹配。{n,}
:匹配前面的元素至少n
次。例如,a{2,}
可以匹配 "aa"、"aaa"、"aaaa" 等,只要a
的连续出现次数不少于 2 次即可。{n,m}
:匹配前面的元素n
到m
次。例如,a{2,4}
可以匹配 "aa"、"aaa"、"aaaa",但不能匹配 "a" 或 "aaaaa"。
6. 方括号([ ])
方括号用于定义一个字符集合,匹配方括号内的任意一个字符。例如,[abc]
可以匹配 "a"、"b" 或 "c"。方括号内还可以使用连字符表示字符范围,如 [a-z]
可以匹配任意小写字母,[0-9]
可以匹配任意数字。此外,方括号内的字符顺序不影响匹配结果,[abc]
和 [cba]
的匹配效果是一样的。
7. 脱字符(^)
脱字符在正则表达式中有两种常见用法:
- 在方括号内使用时,表示取反,即匹配不在方括号内的任意字符。例如,
[^abc]
可以匹配除 "a"、"b"、"c" 之外的任意字符,像 "d"、"1"、"@" 等都能被匹配。 - 在正则表达式开头使用时,表示匹配字符串的开头。例如,
^abc
表示只有以 "abc" 开头的字符串才能被匹配,对于 "xyzabc" 这样的字符串则无法匹配。
8. 美元符号($
)
美元符号用于表示匹配字符串的结尾。例如,abc$
表示只有以 "abc" 结尾的字符串才能被匹配,像 "testabc" 可以匹配,但 "abcdef" 则无法匹配。
^
:表示匹配字符串的开头,确保邮箱地址从这里开始,避免匹配到包含邮箱地址但不是以其开头的字符串。[a-zA-Z0-9._%+-]+
:匹配邮箱用户名部分。方括号内定义了允许的字符集合,包括字母(大小写均可)、数字、点号、下划线、百分号、加号和减号。+
表示这些字符至少出现一次,因为邮箱用户名不能为空。@
:匹配邮箱地址中的 "@" 符号,这是邮箱地址的关键分隔符。[a-zA-Z0-9.-]+
:匹配域名部分,允许包含字母、数字、点号和减号。同样,+
表示这些字符至少出现一次。\.
:匹配域名中的点号,由于点号是元字符,需要使用反斜杠进行转义。[a-zA-Z]{2,}
:匹配顶级域名,由至少两个字母组成。常见的顶级域名如 ".com"、".cn"、".org" 等都满足这个规则。$
:表示匹配字符串的结尾,确保邮箱地址到这里结束,避免匹配到包含邮箱地址但后面还有其他多余字符的情况。
转义字符
当我们需要匹配元字符本身时,就需要使用反斜杠(\
)进行转义。因为元字符在正则表达式中有特殊含义,直接使用会被解释为相应的模式匹配规则。例如,要匹配点号(.
),就需要使用 \.
;要匹配反斜杠本身,需要使用 \\
。在不同的编程语言中,由于字符串本身也可能使用反斜杠进行转义,所以有时需要使用双反斜杠来表示一个真正的反斜杠。例如,在 Python 中,要匹配一个点号,正则表达式字符串需要写成 r'\.'
(使用原始字符串避免 Python 字符串本身的转义)。
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
字符类
代码 | 说明 |
---|---|
[abc] | 只能是 a, b, 或 c |
[^abc] | 除了 a, b, c 之外的任何字符 |
[a-zA-Z] | a 到 z A 到 Z,包括(范围) |
[a-d[m-p]] | a 到 d,或 m 到 p |
[a-z&&[def]] | a-z 和 def 的交集。为:d, e, f |
[a-z&&[^bc]] | a-z 和非 bc 的交集。(等同于 [ad-z]) |
[a-z&&[^m-p]] | a 到 z 和除了 m 到 p 的交集。(等同于 [a-lq-z]) |
限定字符
常用的限定字符如下
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
字符分枝
字符分枝多用于满足不同情况的选择,用“|”将不同的条件分割开来,比如有些固定电话区号有三位,有些有四位,这个时候可以采用字符分枝
例如:\d{3}-\d{8}|\d{4}-\d{8} 可以匹配两种不同长度区号的固定电话
下边的IP地址正则表达式也有用到字符分枝
字符分组
字符分组多用于将多个字符重复,主要通过使用小括号()来进行分组
形如:(\d\w){3} 重复匹配3次(\d\w)
常用于表示IP地址 形如: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
解析:先把IP地址分为两部分一部分是123.123.123. 另一部分是123,又因Ip最大值为255,所以先使用分组,然后在组里边再进行选择,组里也有三部分,0-199,200-249,250-255,分别和上述的表达是对应,最后还要注意分组之后还要加上一个.,因为是元字符所以要转义故加上\. 然后再把这部分整体看做是一个组,重复三次,再加上仅有数字的一组也就是不带\.的那一组即可完成IP地址的校验
懒惰匹配和贪婪匹配
贪婪匹配:正则表达式中包含重复的限定符时,通常的行为是匹配尽可能多的字符。
懒惰匹配,有时候需要匹配尽可能少的字符。
在正则表达式中,默认的匹配模式是贪婪匹配,即尽可能多地匹配字符。例如,正则表达式 a.*b
对于字符串 "aabab" 会匹配整个字符串 "aabab",因为 .*
会尽可能多地匹配字符,直到遇到最后一个 "b"。
而非贪婪匹配则是尽可能少地匹配字符,通过在量词后面加上问号 ?
来实现。例如,正则表达式 a.*?b
对于字符串 "aabab" 会匹配 "aab",因为 .*?
会尽可能少地匹配字符,一旦遇到第一个 "b" 就停止匹配。
八、后向引用
后向引用用于重复搜索前面某个分组匹配的文本。
使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推
示例:\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty。
这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。
你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>\w+)(或者把尖括号换成'也行:(?'Word'\w+)),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b
零宽断言
有时候需要查找某些匹配之前或之后的东西,这个时候就需要用到们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言
零宽断言是一种特殊的正则表达式语法,它用于在不消耗字符的情况下进行位置匹配。也就是说,零宽断言只检查某个位置是否满足特定的条件,而不会将匹配到的字符作为结果的一部分。常见的零宽断言有以下几种:
1. 正向先行断言((? = ...)
)
正向先行断言用于检查某个位置后面的字符是否满足特定的模式。例如,正则表达式 \w+(?=\d)
可以匹配后面紧跟着一个数字的单词字符序列。对于字符串 "abc123",它会匹配 "abc",因为 "abc" 后面紧跟着数字 "1"。
2. 负向先行断言((?!...)
)
负向先行断言用于检查某个位置后面的字符是否不满足特定的模式。例如,正则表达式 \w+(?!\d)
可以匹配后面不紧跟着数字的单词字符序列。对于字符串 "abc def123",它会匹配 "abc" 和 "def",因为 "abc" 后面不是数字,而 "def" 虽然后面跟着数字,但在匹配 "def" 时检查到后面不是数字的条件是满足的。
3. 正向后行断言((?<=...)
)
正向后行断言用于检查某个位置前面的字符是否满足特定的模式。例如,正则表达式 (?<=\$)\d+
可以匹配前面紧跟着美元符号的数字序列。对于字符串 "$123",它会匹配 "123"。
4. 负向后行断言((?<!...)
)
负向后行断言用于检查某个位置前面的字符是否不满足特定的模式。例如,正则表达式 (?<!\$)\d+
可以匹配前面不紧跟着美元符号的数字序列。对于字符串 "123 $456",它会匹配 "123"。
实践
Java 中通过 java.util.regex
包来支持正则表达式的使用。以下是一些示例:
java">import java.util.regex.Matcher;
import java.util.regex.Pattern;public class RegexExample {public static void main(String[] args) {// 匹配手机号码String pattern = "^1[3-9]\\d{9}$";String phone = "13800138000";Pattern r = Pattern.compile(pattern);Matcher m = r.matcher(phone);if (m.matches()) {System.out.println("手机号码格式正确");} else {System.out.println("手机号码格式错误");}// 查找字符串中的单词String text = "Hello, World!";Pattern wordPattern = Pattern.compile("\\w+");Matcher wordMatcher = wordPattern.matcher(text);while (wordMatcher.find()) {System.out.println(wordMatcher.group());}}
}