1 正则表达式
和编译原理中的文法类似,但是是只用一个字符串来描述句子的结构的规则,肉身判断正则的过程类似于英语阅读中划分句子结构。 本文当中正则的部分是根据菜鸟教程总结的
正则表达式的作用
- 测试字符串内的模式,数据验证
- 替换文本
- 基于模式匹配从字符串中提取子字符串
1.1 正则表达式当中的语法
正则表达式是由普通字符以及特殊字符(称为“元字符”)组成的文字模式。
1.1.1 普通字符+元字符
- 普通字符:包括没有显示指定为元字符的所有可打印和不可打印字符。包括所有大写字母、小写字母、所有数字、所有符号和一些其他符号
- 元字符:靠着和普通字符组合在一起形成化学反应
|元字符| 作用 |举例|
|:----|:----|:----|
| \ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转移符| "\n"匹配一个换行符;
"\\"匹配"\";
"\("匹配"("; | ^ | 匹配输入字符串开始位置 | ^Chapter [1-9][0-9]{0,1} 找到章节标题 | | 找到章节标题对应行 | | * | 匹配前面的子表达式子零次或多次 | zo* 能匹配"z"以及"zoo" | | + | 匹配前面的子表达式一次或多次|zo+ 能匹配"zo"以及"zoo"但不能匹配"z"| | ? | 匹配前面的子表达式零次或一次|"do(es)?"可以匹配"do"或"does"| | {n}| 精确匹配前面的子表达式n次,n为非负整数|o{2} 可以匹配"Boob"但不能匹配"Bob"| | {n,}| 最少匹配前面的子表达式n次,n为非负整数| o{3,}可以匹配"Booob"但不能匹配"Boob"| | {n,m}|最少匹配前面的子表达式子n次,且最多匹配前面的子表达式m次,m、n都是正整数且n<=m|o{1,3}能够匹配"noooob"当中前3个o| | ? | 跟在其他限制符(指*,+,?,{n},{n,m})后面,表示匹配模式是非贪婪的,匹配到的字符越少越好,原先默认是贪婪的,匹配到的越多越好|o+?匹配"noob"时只会匹配掉一个o;o+将匹配掉所有o | |(pattern)|匹配pattern并获取这一匹配,如果要匹配圆括号则使用"(" 、")" | | |(?:pattern)| 匹配pattern但不获取匹配结果(非获取匹配),不进行存储供以后使用,在使用"或"组合一个模式时很有用 | cit(?:y|ies),在单词很长的时候比直接写或显得更加简洁| |(?=pattern)|正向肯定预查|Windows(?=95|98|NT|2000)
能够匹配"Windows2000"中的Windows,
但不能匹配"Windows3.1"中的Windows| |(?!pattern)|正向否定预查|Windows(?!95|98|NT|2000)
能够匹配"Windows3.1"中的Windows,
但不能匹配"Windows2000"中的Windows| |(?<=pattern)|反向肯定预查|(?<=95|98|NT|2000)Windows
能够匹配"2000Windows"中的Windows,
但不能匹配"3.1Windows"中的Windows| |(?能够匹配"3.1Windows"中的Windows,
但不能匹配"2000Windows"中的Windows| |a|b|匹配a或者匹配b|z|food能够匹配"z"或者匹配"food"| |[xyz]|字符集合,匹配包含的任意一个字符|[abc]可以匹配"plain"中的a| |[^xyz]|负值字符集合,匹配未包含的任意字符|[^abc]可以匹配"plain"中的p、l、i、n |[a-z]| 字符范围| [a-z] 可以匹配a到z范围内的所有小写字符 |[^a-z]|负值字符范围|[^a-z]可以匹配所有不在'a'到'z'范围内的任意字符| |\b|匹配到一个单词的边界|er\b可以匹配"never"中的er,但不能匹配"verb"中的"er"| |\B|匹配非单词边界|er\B可以匹配"verb"中的er,但不能匹配"never"中的er| |\d|匹配一个数字字符|相当于[0-9]| |\D|匹配一个非数字字符|相当于[^0-9]| |\f|匹配一个换页符|| |\n|匹配一个换行符|| |\r|匹配一个回车符|| |\t|匹配一个制表符|| |\v|匹配一个垂直制表符|| |\s|匹配任何空白字符|相当于[\f\n\r\t\v] |\S|匹配任何非空白字符|相当于[^\f\n\r\t\v] |\w|匹配字母、数字、下划线|等价于[A-Za-z0-9_] |\W|匹配非字母、数字、下划线|等价于[^A-Za-z0--9]
1.1.2 修饰符
修饰符不在正则表达式内部,位于表达式的外边,用于指定额外的匹配策略
/pattern/flags
| 修饰符 | 含义 |
|---|---|
| i | ignore--不区分大小,A和a没有区别 |
| g | global--全局匹配,查找所有的匹配项 |
| m | multi line--多行匹配,使边界字符^和$匹配每一行的开头和结尾,而不是整个字符串的开头和结尾 |
| s | 让圆点在原来能够匹配除换行符\n以外的任何字符外,也能匹配\n |
1.1.3 运算符优先级
正则表达式从左到右进行计算,遵循优先级顺序,与算数表达式类似,相同优先级顺序从左到右,不同优先级运算从高到低。
优先级从高到低排列顺序为
- \ ,转义符
- (),(?:),(?=),[]
- * ,+,?,{n},{n,},{n,m}
- ^ ,$,\任何元字符、任何字符
- |
1.2 尝试用正则表示一个邮箱
分析一下邮箱的组成
- 首先是位于@前面的用于唯一标识用户的用户名,由一些字符组成(具体规则我也不清楚)
- 然后是@
- 位于@后面的是邮箱服务器域名,通常由 标号.标号表示,计算机网络第七版谢希仁记载:标号由字母和数字组成,除了连字符不能出现其他标点符号,有规定标号不能超过63个字符,为了记忆每个标号最好不要超过12个字符。但菜鸟上的邮箱正则表示域名这部分只用字母和-,好像域名中带数字见的比较少
根据上述特点,能够一步步写出相应的正则:
- 首先是用户名开头,1个以上的字符集当中的符号的组合
\b[\w.%+-]+ - 单独一个@
- 邮箱服务器域名
第一部分表示二级域名以及之后的域名组成[\w.-]+
第二部分一个点分隔二级域名和顶级域名 \.
第三部分表示顶级域名结尾 [a-zA-Z]{2,6}\b 拼接在一起就是 \b[\w.%+-]+@[w.-]+\.[a-zA-Z]{2,6}\b
2 用正则验证IP地址是否合法
首先要验证的IP地址分为两类,IPV4和IPV6
2.1 IPV4地址
一般所说的IP地址默认是指IPV4地址,有32位,用点分十进制表示,每一个8位用一个十进制数(0-255)表示,不允许有零开头的数字,比如01这种。
而它的正则我们可以只写一个,然后将IPV4地址按照.分割成4部分,逐一判断;我们也可以写好一个8位后再写出完整的IPV4地址的正则
- 首先考虑一个8位怎么写,包括所有1位数、所有两位数、100-200、200-249、250-255
String chunkIPV4 ="([0-9] | [1-9][0-9] | 1[0-9]{2} | 2[0-4][0-9] | 25[0-5])"
- 然后用一个8位的正则拼成一个IPV4, 开头一个8位+. * 3 + 一个8位结尾
Pattern pattenIPv4 = Pattern.compile("^(" + chunkIPv4 + "\\.){3}" + chunkIPv4 + "$");
2.2 IPV6地址
IPV6采用冒号十六进制记法
- IPV6的地址128位,用16进制数表示
- 每组16个比特,一共8组
- 组与组之间用 : 间隔
- 一个16进制数可以表示4个比特,一组4个十六进制数
- 字母不区分大小写,可以0开头但不能有多余的零(不能改变16进制数的位数)
另外,题目规定不能使用零压缩,也就是如果中间有一组16进制数为0不能将其省略为::,这个有点救命了,不然确实会变得复杂
- 首先写出1组的正则,四个16进制数,前导0可要也可不要,不区分大小写
String chunkIPv6 = "([0-9a-fA-F]{1,4})";
- 然后用其拼成IPV6的正则, 开头 一个组 + : * 7 + 一个组结尾
Pattern pattenIPv6 = Pattern.compile("^(" + chunkIPv6 + "\\:){7}" + chunkIPv6 + "$");
2.3 代码
如果包含.代表要匹配的是IPV4,如果包含:代表要匹配的是IPV6,否则直接不匹配
import java.util.regex.Pattern;
class Solution {
String chunkIPv4 = "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
Pattern pattenIPv4 =
Pattern.compile("^(" + chunkIPv4 + "\\.){3}" + chunkIPv4 + "$");
String chunkIPv6 = "([0-9a-fA-F]{1,4})";
Pattern pattenIPv6 =
Pattern.compile("^(" + chunkIPv6 + "\\:){7}" + chunkIPv6 + "$");
public String validIPAddress(String IP) {
if (IP.contains(".")) {
return (pattenIPv4.matcher(IP).matches()) ? "IPv4" : "Neither";
}
else if (IP.contains(":")) {
return (pattenIPv6.matcher(IP).matches()) ? "IPv6" : "Neither";
}
return "Neither";
}
}