正则表达式
元字符
\w匹配一个字符,可以是数字、下划线、字母或者汉字\b匹配的是位置,它的前一个字符和后一个字符不全是(一个是, 一个不是或不存在)\w
\bhi\b匹配hi单词, 对于history和him都不匹配\bhi\b.*\blucy\bhi单词后面若干字符之后是lucy
.匹配除了换行符之外的任一字符\bhi.lucy\b在hi和lucy中存在一个除了换行符之外的任一字符*匹配的是数量 0~若干个\bhi\b.*\blucy\bhi单词后面若干个除换行符以外的字符之后是lucy\s匹配任意的空白符\d匹配数字0\d{2}-\d{8}匹配以0开头,紧接着是2位数字,接着是-,最后是8位数字\d+匹配多于一个数字[0123456789]或者[0-9]匹配数字与\d的意义完全相同^匹配字符串的开始$匹配字符串的结束^\b{5,12}$匹配5到12位的数字
字符转义
使用斜杠\对特殊字符转义,转换为普通字符
重复
| 代码/语法 | 说明 |
|---|---|
* |
重复零次或更多次 |
+ |
重复一次或者更多次 |
? |
重复零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m次 |
windows\d+ windows后面紧跟着多于一个数字^\w+ 以多于一个字符开头
字符类
[aeiou]aeiou中的一个字符[0-9]从0到9[0-9A-Za-z]完全等同于\w(如果不考虑中文字符)[) -]从右括号、空格和减号中选择一个\(?0\d{2}[) -]?\d{8}首先是一个转义字符\(, 它可能出现0次或1次(?), 然后是一个0, 后面跟着2个数字\d{2},然后是)或-或空格中的一个,它出现1此或不出现(?), 最后是8个数字(\d{8}).
分枝条件
分枝条件: 满足若干条件中的一个就能匹配. 要注意优先级和各条件顺序问题.
** 例1 匹配电话号码
要匹配两种电话号码形式: (0755)12345678 或者 0755-12345678 以及 0751-1234567
\(\d{3,4}\)\d{7,8}|\d{3,4}-\d{7,8}
** 例2: 注意分枝条件各条件的顺序
美国的邮编是有两种形式的: 5位数字 或者用连字号间隔的9位数字, 如12345和12345-1234两种形式.
应该使用\d{5}-\d{4}|\d{5}, 而不能使用\d{5}|\d{5}-\d{4}
也就是说, 如果\d{5}提前匹配了12345之后会返回而丢弃掉后面的四位.
分组
分组可以实现子表达式重复出现.
(\d{1,3}\.){3}\d{1,3} 匹配以点号分割的四组数字, 每组数字是1到3位,如 999.999.999.999 或1.1.0.0
** IP地址的匹配 **
IP地址分为4段, 每段的数字范围为: 0~255
正则表达式只能匹配字符串的格式, 不支持数值比较, 因此需要用尽可能精简的方式准确列出所有的可能性
((2[0-4]\d|25[0-5]|[0-1]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[0-1]?\d\d?)
解释:
2[0-4]\d首位是2,第二位可以为0到4,第三位是任意数字25[0-5]前两位是25,第三位必须是0到5, 因为最大是255[0-1]?\d\d?首位是0或1, 也可以首位不存在, 第二位为任何数字, 第三位是任意数字, 也可以不存在, 不存在这种情况下, 就只有前两位.
为什么不是[0-1]?\d?\d?
反义
对字符类进行反义, 只需要将上面字符类一节的所有的字符转为大写
| 代码/语法 | 说明 |
|---|---|
\W |
匹配任意不是字母,数字,下划线,汉字的字符 |
\S |
匹配任意不是空白符的字符 |
\D |
匹配任意非数字的字符 |
\B |
匹配不是单词开头或结束的位置 |
[^x] |
匹配除了x以外的任意字符 |
[^aeiou] |
匹配除了aeiou这几个字母以外的任意字符 |
后向引用
用于引用上文中匹配到的字符- 给上文中出现的表达式添加组号, 在下文中引用这个组号
| 代码/语法 | 说明 |
|---|---|
(exp) |
匹配exp,并捕获文本到自动命名的组里 |
(?<name>exp) |
匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp) |
(?:exp) |
匹配exp,不捕获匹配的文本,也不给此分组分配组号 |
分组命名规则为:
- 分组0对应整个正则表达式
- 实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号
- 你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.
例1 自动命名\b(\w+)\b\s+\1\b 首先匹配一个单词(\w+)并放到分组1中, 在下文中通过\1引用上文中分组1匹配到的字符.
该表达式用于匹配重复单词. 如 hello hello
例2 手动命名\b(?<name>\w+)\b\s+\k<name>\b 首先匹配一个单词(\w+) 并将这个单词放入name组中, 在下文中引用name分组, 看后面是否再出现
注意反向引用时的格式: \k<name>
例3 不捕获
\b(?:\w+)\b\s+ 不捕获
零宽断言
零宽断言和负向零宽断言用于指示位置, 零宽断言分为两种, 分别指示匹配表达式的前面和后面.
零宽度正预测先行断言
用于指示匹配表达式的字符串的上一个字符位置
\b\w+(?=ing\b) 匹配以ing结尾的单词的前面部分(除了ing以外的部分).
如果查找 I'm singing while you're dancing.时, 它会匹配sing和danc.
零宽度正回顾后发断言
用于指示匹配表达式的字符串的下一个字符位置
(?<=\bre)\w+\b 匹配以re开头的单词的后半部分
((?<=\d)\d{3}+\b)匹配一个数字后面是若干的三个数字的后面部分.
对1234567890进行查找时结果是234567890
负向零宽断言
负向零宽断言, 与上一节中的正向零宽断言相对, 用于匹配不是某个字符或不在某些字符类里的方法
确保某个字符没有出现, 但并不想去匹配它, 就可以使用负向零宽断言
零宽度负预测先行断言(?!exp)
断言此位置的后面不能匹配表达式exp。
例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;
\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
零宽度负回顾后发断言
同理,可以用(?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7} 匹配前面不是小写字母的七位数字。
请详细分析表达式(?<=<(\w+)>).*(?=<\/\1>),这个表达式最能表现零宽断言的真正用途。
一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容。(?<=<(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是),
然后是.*(任意的字符串),最后是一个后缀(?=</\1>)。注意后缀里的/,
它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,
前面的(\w+)匹配的内容,这样如果前缀实际上是的话,后缀就是了。
整个表达式匹配的是和之间的内容(再次提醒,不包括前缀和后缀本身)。
注释
通过语法(?#comment)来包含注释
1 | (?<= # 断言要匹配的文本的前缀 |
贪婪与懒惰
| 代码/语法 | 说明 |
|---|---|
*? |
重复任意次,但尽可能少重复 |
+? |
重复1次或更多次,但尽可能少重复 |
?? |
重复0次或1次,但尽可能少重复 |
{n,m}? |
重复n到m次,但尽可能少重复 |
{n,}? |
重复n次以上,但尽可能少重复 |
处理选项
| 名称 | 说明 |
|---|---|
IgnoreCase(忽略大小写) |
匹配时不区分大小写。 |
Multiline(多行模式) |
更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |
Singleline(单行模式) |
更改.的含义,使它与每一个字符匹配(包括换行符\n)。 |
IgnorePatternWhitespace(忽略空白) |
忽略表达式中的非转义空白并启用由#标记的注释。 |
ExplicitCapture(显式捕获) |
仅捕获已被显式命名的组。 |
平衡组/递归匹配
用于在嵌套的层次结构中, 判断是否嵌套.
(?'group')把捕获的内容命名为group,并压入堆栈(Stack)(?'-group')从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败(?(group)yes|no)如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分(?!)零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
1 | < #最外层的左括号 |
我们需要做的是每碰到了左括号,就在压入一个”Open”,每碰到一个右括号,就弹出一个,到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。
其他语法
| 代码/语法 | 说明 |
|---|---|
| \a | 报警字符(打印它的效果是电脑嘀一声) |
| \b | 通常是单词分界位置,但如果在字符类里使用代表退格 |
| \t | 制表符,Tab |
| \r | 回车 |
| \v | 竖向制表符 |
| \f | 换页符 |
| \n | 换行符 |
| \e | Escape |
| \0nn | ASCII代码中八进制代码为nn的字符 |
| \xnn | ASCII代码中十六进制代码为nn的字符 |
| \unnnn | Unicode代码中十六进制代码为nnnn的字符 |
| \cN | ASCII控制字符。比如\cC代表Ctrl+C |
| \A | 字符串开头(类似^,但不受处理多行选项的影响) |
| \Z | 字符串结尾或行尾(不受处理多行选项的影响) |
| \z | 字符串结尾(类似$,但不受处理多行选项的影响) |
| \G | 当前搜索的开头 |
| \p{name} | Unicode中命名为name的字符类,例如\p{IsGreek} |
| (?>exp) | 贪婪子表达式 |
| (? |
平衡组 |
| (?im-nsx:exp) | 在子表达式exp中改变处理选项 |
| (?im-nsx) | 为表达式后面的部分改变处理选项 |
| (?(exp)yes | no) |
| (?(exp)yes) | 同上,只是使用空表达式作为no |
| (?(name)yes | no) |
| (?(name)yes) | 同上,只是使用空表达式作为no |
常用的正则表达式
电子邮箱Email
\w+([-+.]\w+)*@\w+([-.]\w)*\.\w+([-.]\w+)*
Email地址是以@分割, @前面的部分为用户名, 后面为域名.
用户名部分可以包含字母/数字/下划线/-/+/., 但是只能字母/数字/下划线开头.
不能出现+-.连续
而域名部分可以包含字母/数字/下划线/-以及. 也是只能字母/数字/下划线开头.
用户名部分\w+([-+.]\w+)* , 必须是至少一个\w开头,
手机号码
[1-9][3,4,5,7,8]/d{9}
汉字
[\u4e00-\u9fa5]
QQ号码
[1-9]\d{4,}
邮编
[1-9]\d{5}
不含abc的单词
\b((?!abc)\w)+\b
时间(小时:分钟, 24小时制)
((1|0?)[0-9]|2[0-3]):([0-5][0-9])
姓名
1 | String regex="^([(?<cc>a-zA-Z\\u4e00-\\u9fa5)]+)|([a-zA-Z\\u4e00-\\u9fa5]+[a-zA-Z\\u4e00-\\u9fa5. _·]*)$"; |
1 | 张大千: √ |
Java中运用
java.util.regex 包主要由三个类所组成:Pattern、Matcher 和 PatternSyntaxException。
- Pattern 对象表示一个已编译的正则表达式。Pattern 类没有提供公共的构造方法。要构建一个模式,首先必须调用公共的静态
compile方法,它将返回一个 Pattern 对象。这个方法接受正则表达式作为第一个参数。 - Matcher 是一个靠着输入的字符串来解析这个模式和完成匹配操作的对象。与 Pattern 相似,Matcher 也没有定义公共的构造方法,需要通过调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
- PatternSyntaxException 对象是一个未检查异常,指示了正则表达式中的一个语法错误。
Pattern
1 | String message=""; |
** matches(String, CharSequence) 方法 **
Pattern 类定义了一个方便的 matches 方法,用于快速地检查模式是否表示给定的输入字符串。与使用所有的公共静态方法一样,应该通过它的类名来调用 matches 方法,诸如 Pattern.matches(“\d”,”1”);。这个例子中,方法返回 true,这是由于数字“1”匹配了正则表达式\d。
** split(String) 方法 **
1 | String REGEX = ":"; |
** public static String quote(String s)** :返回指定字符串字面模式的字符串。此方法会产生一个字符串,能被用于构建一个与字符串 s 匹配的 Pattern,好像它是一个字面上的模式。输入序列中的元字符和转义序列将没有特殊的意义了。
** public String toString() ** :返回这个模式的字符串表现形式。这是一个编译过的模式中的正则表达式。
** java.lang.String中等价的方法 **
java.lang.String 通过模拟 java.util.regex.Pattern 行为的几个方法,也可以支持正则表达式。方便起见,下面主要摘录了出现在 API 关键的方法。
public boolean matches(String regex):告知字符串是否匹配给定的正则表达式。调用str.matches(regex)方法所产生的结果与作为表达式的Pattern.matches(regex, str)的结果是完全一致。public String[] split(String regex, int limit):依照匹配给定的正则表达式来拆分字符串。调用str.split(regex, n)方法所产生的结果与作为表达式的Pattern.compile(regex).split(str, n)的结果完全一致。public String[] split(String regex):依照匹配给定的正则表达式来拆分字符串。这个方法与调用两个参数的 split 方法是相同的,第一个参数使用给定的表达式,第二个参数限制为 0。在结果数组中不包括尾部的空字符串。- 还有一个替换方法,把一个
CharSequence替换成另外一个:public String replace(CharSequence target,CharSequence replacement):将字符串中每一个匹配替换匹配字面目标序列的子字符串,替换成指定的字面替换序列。这个替换从字符串的开始处理直至结束,例如,把字符串“aaa”中的“aa”替换成“b”,结果是“ba”,而不是“ab”。
Matcher
1 |
|
** 使用 start 和 end 方法 **
1 | String REGEX = "\\bdog\\b"; |
** 使用 matches 和 lookingAt 方法 **
1 | String REGEX = "foo"; |
** 使用 replaceFirst(String) 和 replaceAll(String) 方法 **
1 | String REGEX = "dog"; |
** 使用 appendReplacement(StringBuffer, String) 和
appendTail(StringBuffer) 方法 **
1 | String REGEX = "a*b"; |
** 在 java.lang.String 中等价的 Matcher 方法 **
为了使用方便,String 类看上去还不错地模仿了 Matcher 的两个方法:
public String replaceFirst(String regex, String replacement):使用给定的替换字符串替换该字符串中匹配了给定正则表达式的第一个子字符串。调用 str.replaceFirst(regex, repl)方法与使用 Pattern.compile(regex).matcher(str).replaceFirst(repl)产生的结果是完全相同的。
public String replaceAll(String regex, String replacement):使用给定的替换字符串替换该字符串中匹配了给定正则表达式的每一个子字符串。调用 str.replaceAll(regex, repl)方法与使用 Pattern.compile(regex).matcher(str).replaceAll(repl)产生的结果是完全相同的。
PatternSyntaxException
PatternSyntaxException 是未检查异常,指示正则表达式模式中的语法错误。PatternSyntaxException 类提供了下面的一些方法,用于确定在什么地方发生了错误:
- public String getDescription():获得错误描述。
- public int getIndex():获得错误索引。
- public String getPattern():获得字符串形式的错误正则表达式。
- public String getMessage():获得一个多行的字符串,包括语法错误和错误的索引、错误的正则表达式模式,以及模式内可视化的索引指示。
1 | Pattern pattern = null; |
执行结果
1 | Enter your regex: ?i) |
问题与练习
1 | 〖问题〗 |
使用
替换字符串中的字符串
1 | String regularExpressionString=...; |
参考文献: