1. 日常开发中,常用的正则表达式有哪些
- 邮编
- 电话号码,包括手机号码和固定电话号码
- 日期和时间
- 身份证号
- IP 地址
- URL
- Email 地址
- 中文字符
2. 写正则表达式时的细节有
- 对于同一个目的,正则表达式往往有多种写法,大多没有唯一正确的写法
- 写一个正则表达式,匹配希望匹配的内容往往比较容易,但让它不匹配不希望匹配的内容则往往比较困难。即,保证精确性经常是很难的
- 很多时候,也没有必要写完全精确的表达式,需要写到多精确与需要处理的文本和需求有关
- 正则表达式难以表达的,可以通过写程序进一步处理
3. 写一个匹配邮编的正则表达式
邮编是 6 位数字
[0-9]{6}
:如果用于查找,这个表达式是不够的1
2
3
4
5
6//验证输入是否为邮编
public static Pattern ZIP_CODE_PATTERN = Pattern.compile("[0-9]{6}");
public static boolean isZipCode(String text) {
return ZIP_CODE_PATTERN.matcher(text).matches();
}(?<![0-9])[0-9]{6}(?![0-9])
:环视边界匹配1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//用于查找
public static Pattern ZIP_CODE_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "[0-9]{6}"
+ "(?![]0-9)" //右边不能有数字
);
public static void findZipCode(String text) {
Matcher matcher = ZIP_CODE_PATTERN.matcher(text);
while(matcher.find()) {
System.out.println(matcher.group());
}
}
public static void main(String[] args) {
findZipCode("邮编 100013, 电话 18612345678");
}
6 位数字不一定是邮编,如果需要更精确的验证,可以写程序进一步检查
4. 写一个匹配手机号码的正则表达式
[0-9]{11}
:11 数字,最简单的表达式1[34578][0-9]{9}
:第 1 位是 1,第 2 位取值 34
57
8 之一1[34578][0-9]-?[0-9]{4}-?[0-9]{4}
:带有两个连字符的表示形式(?<![0-9])((0|\+86|0086)\s?)?1[34578][0-9]-?[0-9]{4}-?[0-9]{4}(?![0-9])
:在手机号前面,可能还有 0、+86 或 0086,和手机号码之间可能还有一个空格;和邮编类似,如果为了抽取,也要在左右加环视边界匹配,左右不能是数字。用 Java 表示的代码为1
2
3
4
5
6public static Pattern MOBILE_PHONE_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "((0|\\+86|0086)\\s?)?" //0 +86 0086
+ "1[34578][0-9]-?[0-9]{4}-?[0-9]{4}" //186-1234-5678
+ "(?![0-9])"); //右边不能有数字
);
5. 写一个匹配固定电话号码的正则表达式
不考虑分机,中国的固定电话一般由两部分组成:区号和市内号码。区号以 0 开头,3 到 4 位;市号是 7 到 8 位
区号:0[0-9]{2,3}
市号:[0-9]{7,8}
(\(?0[0-9]{2,3}\)?-?)?[0-9]{7,8}
:区号可能用括号包含,区号与市号之间可能有连字符,整个区号是可选的再加上左右边界环视,完整的 Java 表达式为
1
2
3
4
5
6public static Pattern FIXED_PHONE_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "(\\(?0[0-9]{2,3}\\)?-?)?" //区号
+ "[0-9]{7,8}" //市内号码
+ "(?![0-9])" //右边不能有数字
);
6. 写一个表示日期的正则表达式(形如 2016-11-21)
年月日之间用连字符分隔,月和日可能只有一位。年一般没有限制,但月只能取值 1
12,日只能取值 131对于月,有两种情况:1 月到 9 月,表达式可以为:
0?[1-9]
;10 月到 12 月,表达式可以为:1[0-2]
。所以,月的表达式为:(0?[1-9]|1[0-2])
对于日,有三种情况:1 到 9 号,表达式为:
0?[1-9]
;10 号到 29 号,表达式为:[1-2][0-9]
;30 号到 31 号,表达式为:3[0-1]
所以,整个表达式为:
\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][0-9]|3[0-1])
加上左右边界环视,完整的 Java 表示为
1
2
3
4
5
6
7public static Pattern DATE_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "\\d{4}" //年
+ "(0?[1-9]|1[0-2])-" //月
+ "(0?[1-9]|[1-2][0-9]|3[01])" //日
+ "(?![0-9])" //右边不能有数字
);
7. 写一个表示时间的正则表达式
考虑 24 小时制,只考虑小时和分钟 ,小时和分钟都用固定两位表示。格式比如:10:57,基本表达式为:
\d{2}:\d{2}
小时取值范围为 0~23,更精确的表达式为:
([0-1][0-9]|2[0-3])
分钟取值范围为 0~59,更精确的表达式为:
[0-5][0-9]
所以,整个表达式为:
([0-1][0-9]|2[0-3]):[0-5][0-9]
加上左右边界环视,完整的 Java 表示为
1
2
3
4
5
6public static Pattern TIME_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "([0-1][0-9]|2[0-3])" //小时
+ ":" + "[0-5][0-9]" //分钟
+ "(?![0-9])" //右边不能有数字
);
8. 写一个表示身份证号的正则表达式
身份证有一代和二代之分,一代身份证号是 15 位数字,二代身份证号是 18 位数字,都不能以 0 开头。对于二代身份证,最后一位可能为 x 或 X,其他是数字(符合这个要求的不一定就是身份证号,身份证号还有一些更为具体的要求)
一代身份证号表达式可以为:
[1-9][0-9]{14}
二代身份证号表达式可以为:
[1-9][0-9]{16}[0-9xX]
上面两个表达式的前面部分是相同的,二代身份证号表达式多了如下内容:
[0-9]{2}[0-9xX]
。所以,它们可以合并为一个表达式,即:[1-9][0-9]{14}([0-9]{2}[0-9xX])?
加上左右边界环视,完整的 Java 表示为
1
2
3
4
5
6public static Pattern ID_CARD_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "[1-9][0-9]{14}" //一代身份证
+ "([0-9]{2}[0-9xX])?" //二代身份证多出的部分
+ "(?![0-9])" //右边不能有数字
);
9. 写一个表示 IP 地址的正则表达式
IP 地址示例如下:192.168.3.5。点号分隔,4 段数字,每个数字范围是 0~255。最简单的表达式为:
(\d{1,3}\.){3}\d{1-3}
\d{1,3}
太简单,没有满足 0~255 之间的约束,要满足这个约束,需要分多种情况考虑值是一位数,前面可能有 0~2 个 0,表达式为:
0{0,2}[0-9]
值是两位数,前面可能有一个 0,表达式为:
0?[0-9]{2}
值是三位数,又要分为多种情况
- 以 1 开头的,后两位没有限制,表达式为:
1[0-9]{2}
- 以 2 开头的,如果第二位是 0 到 4,则第三位没有限制,表达式为:
2[0-4][0-9]
- 如果第二位是 5,则第三位取值为 0 到 5,表达式为:
25[0-5]
- 以 1 开头的,后两位没有限制,表达式为:
所以,
\d{1,3}
更为精确的表示为:(0{0,2}[0-9]|0?[0-9]{2}|1[0-9]{2}[0-4][0-9]|25[0-5])
加上左右边界环视,IP 地址的完整 Java 表示为
1
2
3
4
5
6public static Pattern IP_PATTERN = Pattern.compile(
"(?<![0-9])" //左边不能有数字
+ "((0{0,2}[0-9]|0?[0-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}"
+ "(0{0,2}[0-9]|0?[0-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0-25])"
+ "(?![0-9])" //右边不能有数字
);(以上答案待商榷)
10. 写一个表示 URL 的正则表达式
URL 的格式比较复杂,其规范定义在 https://tools.ietf.org/html/rfc1738。这里只考虑 HTTP 协议,其通用格式是:http://
: / ? 开始是 http://,接着是主机名,主机名之后是可选的端口,再之后是可选的路径,路径后是可选的查询字符串,以 ? 开头。比如,http://www.example.com:8080/ab/c/def?q1=abc&q2=def
主机名中的字符可以是字母、数字、减号和点号,所以表达式可以为:
[-0-9a-zA-Z.]+
端口部分可以写为:
(:\d+)?
路径由多个子路径组成,每个子路径以 / 开头,后跟零个或多个非 / 的字符。简单地说,表达式可以为:
(/[^/]*)*
;更精确地说,把所有允许的字符列出来,表达式为:(/[-\w$.+!*'(),%;:@&=]*)*
对于查询字符,简单地说,由非空字符串组成,表达式为:
\?[\S]*
;更精确地说,把所有允许的字符列出来,表达式为:\?[-\w$.+!*'(),%;:@&=]*
路径和查询字符串是可选的,且查询字符串只有在至少存在一个路径的情况下才能出现,其模式为:
(/<sub_path>(/<sub_path>)*(\?<search>)?)?
。所以,路径和查询部分的简单表达式为:(/[^/]*(/[^/]*)*(\?[\S]*)?)?
;精确表达式为:(/[-\s$.+!*'(),%;:@&=]*(/[-\w$.+!*'(),%;:@&=]*)*(\?[-\w$.+!*'(),%;:@&=]*)?)?
HTTP 正则表达式的完整 Java 表达式为
1
2
3
4
5
6
7
8
9public static Pattern HTTP_PATTERN = Pattern.compile(
"http://" + "[-0-1a-zA-Z.]+" //主机名
+ "(:\\d+)?" //端口
+ "(" //可选的路径和查询 - 开始
+ "/[-\\w$.+!*'(),%;:@&=]*" //第一层路径
+ "(/[-\\w$.+!*'(),%;:@&=]*)*" //可选的其他层路径
+ "(\\?[-\\w$.+!*'(),%;:@&=]*)?" //可选的查询字符串
+ ")?" //可选的路径和查询 - 结束
);
11. 写一个 Email 地址的正则表达式
完整的 Email 规范比较复杂,定义在 https://tools.ietf.org/html/rfc822
新浪邮箱
举例:abc@sina.com
对于用户名部分,它的要求是 4~16 个字符,可使用英文小写、数字、下划线,但下划线不能在首尾
验证用户名,可以为:
[a-z0-9][a-z0-9_]{2,14}[a-z0-9]
完整的 Java 表达式为
1
2
3
4
5public static Pattern SINA_EMAIL-PATTERN = Pattern.compile(
"[a-z0-9]"
+ "[a-z0-9_]{2,14}"
+ "[a-z0-9]@sina\\.com"
);
QQ 邮箱
3~18 个字符,可使用英文、数字、减号、点号或下划线
必须以英文字母开头,必须以英文字母或数字结尾
点号、减号、下划线不能连续出现两次或两次以上
如果只有第 1 条,可以为:[-0-9a-zA-Z._]{3,18}
为满足第 2 条,可以改为:[a-zA-Z][-0-9a-zA-Z._]{1,16}[a-zA-Z0-9]
使用边界环视满足第 3 条,左边加如下表达式:(?![-0-9a-zA-Z.]*(–|..|_))
完整表达式可以为:(?![-0-9a-zA-Z.]*(–|..|_))[a-zA-Z][-0-9a-zA-Z._]{1,16}[a-zA-Z0-9]
完整的 Java 表达式为
1
2
3
4
5
6
7public static Pattern QQ_EMAIL_PATTERN = Pattern.compile(
//点号、减号、下划线不能连续出现两次或两次以上
"(?![-0-9a-zA-Z._]*(--|\\.\\.|__))"
+ "[a-zA-Z]" //必须以英文字母开头
+ "[-0-9a-zA-Z._]{1,16}" //3~18位英文、数字、减号、点号、下划线组成
+ "[a-zA-Z0-9]@qq\\.com" //由英文字母、数字结尾
);
一般的邮箱的规则是:以
@
作为分隔符,前面是用户名,后面是域名用户名的一般规则是
- 由英文字母、数字、下划线、减号、点号组成
- 至少 1 位,不超过 64 位
- 开头不能是减号、点号和下划线
- 比如,h_llo-abc.good@example.com,表达式可以为:[0-9a-zA-Z][-._0-9a-zA-Z]{0,63}
域名部分以点号分隔为多个部分,至少有两个部分
最后一部分是顶级域名,由 2~3 个英文字母组成,表达式可以为:[a-zA-Z]{2,3}
对于域名的其他点号分隔的部分,每个部分一般由字母、数字、减号组成,但减号不能在开头,长度不能超过 63 个字符,表达式可以为:[0-9a-zA-Z][-0-9a-zA-Z]{0,62}.)+[a-zA-Z]{2,3}
完整的 Java 表示为
1
2
3
4
5
6public static Pattern GENERAL_EMAIL_PATTERN = Pattern.compile(
"[0-9a-zA-Z][-._0-9a-zA-Z]{0,63}" //用户名
+ "@"
+ "([0-9a-zA-Z][-0-9a-zA-Z]{0,62}\\.)+" //域名部分
+ "[a-zA-Z]{2,3}" //顶级域名
);
12. 写一个匹配中文字符的正则表达式
- 中文字符的 Unicode 编号一般位于 \u4e00~\u9fff 之间,所以匹配任意一个中文字符的表达式可以为:
[\u4e00-\u9fff]
- Java 表达式为:
public static Pattern CHINESE_PATTERN = Pattern.compile("[\\u4e00-\\u9fff]");