欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

常见正则表达式剖析

程序员文章站 2022-07-13 13:32:36
...

没错,又是正则,没办法,这东西入门很简单,但真正能写好,那是真心难,继续学吧。。。
基本语法我在这里就不赘述了,需要的话可以关注我公众号,里面有很详细的语法介绍和示例。

今天我们来针对几个常见的正则来慢慢刨析

电话号码

这个应该是最常用的,没有之一了吧

  • 手机号

中国的手机号码都是11位数字,所以,最简单的表达式就是:

[0-9]{11}

不过,目前手机号第1位都是1,第2位取值为3、4、5、7、8之一,所以,更精确的表达式是:

1[3|4|5|7|8|][0-9]{9}

为方便表达手机号,手机号中间经常有连字符(即减号’-’),形如:
186-1234-5678

为表达这种可选的连字符,表达式可以改为:

1[3|4|5|7|8|][0-9]-?[0-9]{4}-?[0-9]{4}

在手机号前面,可能还有0、+86或0086,和手机号码之间可能还有一个空格,比如:
018612345678
+86 18612345678
0086 18612345678

为表达这种形式,可以在号码前加如下表达式:

((0|\+86|0086)\s?)?

如果为了抽取,也要在左右加环视边界匹配,左右不能是数字。所以,完整的表达式为:

(?<![0-9])((0|\+86|0086)\s?)?1[3|4|5|7|8|][0-9]-?[0-9]{4}-?[0-9]{4}(?![0-9])

用Java表示的代码为:

public static Pattern MOBILE_PHONE_PATTERN = Pattern.compile(
     "(?<![0-9])" // 左边不能有数字
     + "((0|\\+86|0086)\\s?)?" // 0 +86 0086
     + "1[3|4|5|7|8|][0-9]-?[0-9]{4}-?[0-9]{4}" // 186-1234-5678
     + "(?![0-9])"); // 右边不能有数字   
  • 固定电话

不考虑分机,中国的固定电话一般由两部分组成:区号和市内号码,区号是3到4位,市内号码是7到8位。区号以0开头,表达式可以为:

0[0-9]{2,3}

市内号码表达式为:

[0-9]{7,8}

区号可能用括号包含,区号与市内号码之间可能有连字符,如以下形式:
010-62265678
(010)62265678

整个区号是可选的,所以整个表达式为:

(\(?0[0-9]{2,3}\)?-?)?[0-9]{7,8}

再加上左右边界环视,完整的Java表示为:

public static Pattern FIXED_PHONE_PATTERN = Pattern.compile(
     "(?<![0-9])" // 左边不能有数字
     + "(\\(?0[0-9]{2,3}\\)?-?)?" // 区号
     + "[0-9]{7,8}"// 市内号码
     + "(?![0-9])"); // 右边不能有数字

身份证号码

身份证有一代和二代之分,一代是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表示为:

public static Pattern ID_CARD_PATTERN = Pattern.compile(
    "(?<![0-9])" // 左边不能有数字
    + "[1-9][0-9]{14}" // 一代身份证
    + "([0-9]{2}[0-9xX])?" // 二代身份证多出的部分
    + "(?![0-9])"); // 右边不能有数字

符合这个要求的就一定是身份证号码吗?当然不是,身份证还有一些其他的要求,这里就不探究了

邮箱

完整的Email规范比较复杂,我们先看一些实际中常用的。

比如新浪邮箱,它的格式如:

[email protected]

对于用户名部分,它的要求是:4-16个字符,可使用英文小写、数字、下划线,但下划线不能在首尾。
怎么验证用户名呢?可以为:

[a-z0-9][a-z0-9_]{2,14}[a-z0-9]

新浪邮箱的完整Java表达式为:

public static Pattern SINA_EMAIL_PATTERN = Pattern.compile(
     "[a-z0-9]" 
     + "[a-z0-9_]{2,14}"
     + "[a-z0-9]@sina\\.com");  

我们再来看QQ邮箱,它对于用户名的要求为:

  1. 3-18字符,可使用英文、数字、减号、点或下划线
  2. 必须以英文字母开头,必须以英文字母或数字结尾
  3. 点、减号、下划线不能连续出现两次或两次以上

如果只有第一条,可以为:

[-0-9a-zA-Z._]{3,18}

为满足第二条,可以改为:

[a-zA-Z][-0-9a-zA-Z._]{1,16}[a-zA-Z0-9]

怎么满足第三条呢?可以使用边界环视,左边加如下表达式:

(?![-0-9a-zA-Z._]*(--|\.\.|__))

完整表达式可以为:

(?![-0-9a-zA-Z._]*(--|\.\.|__))[a-zA-Z][-0-9a-zA-Z._]{1,16}[a-zA-Z0-9]

QQ邮箱的完整Java表达式为:

public 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. 由英文字母、数字、下划线、减号、点号组成
  2. 至少1位,不超过64位
  3. 开头不能是减号、点号和下划线

比如:
[email protected]

这个表达式可以为:

[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}

所以,域名部分的表达式为:

([0-9a-zA-Z][-0-9a-zA-Z]{0,62}\.)+[a-zA-Z]{2,3}

完整的Java表示为:

public 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}"); // *域名      

日期

日期的表示方式有很多种,我们只看一种,形如:
2017-06-21
2016-11-1

年月日之间用连字符分隔,月和日可能只有一位。
最简单的正则表达式可以为:

\d{4}-\d{1,2}-\d{1,2}

年一般没有限制,但月只能取值1到12,日只能取值1到31,怎么表达这种限制呢?

对于月,有两种情况,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[01]
所以,整个表达式为:

\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][0-9]|3[01])

加上左右边界环视,完整的Java表示为:

public 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])"); // 右边不能有数字

URL

URL的格式比较复杂,我们只考虑http协议,其通用格式是:

http://<host>:<port>/<path>?<searchpart>

开始是http://,接着是主机名,主机名之后是可选的端口,再之后是可选的路径,路径后是可选的查询字符串,以?开头。

主机名中的字符可以是字母、数字、减号和点号,所以表达式可以为:

[-0-9a-zA-Z.]+

端口部分可以写为:

(:\d+)?

路径由多个子路径组成,每个子路径以/开头,后跟零个或多个非/的字符,简单的说,表达式可以为:

(/[^/]*)*

更精确的说,把所有允许的字符列出来,表达式为:

(/[-\w$.+!*'(),%;:@&=]

对于查询字符串,简单的说,由非空字符串组成,表达式为:

\?[\S]*

更精确的,把所有允许的字符列出来,表达式为:

\?[-\w$.+!*'(),%;:@&=]*

路径和查询字符串是可选的,且查询字符串只有在至少存在一个路径的情况下才能出现,其模式为:

(/<sub_path>(/<sub_path>)*(\?<search>)?)?

所以,路径和查询部分的简单表达式为:

(/[^/]*(/[^/]*)*(\?[\S]*)?)?

精确表达式为:

(/[-\w$.+!*'(),%;:@&=]*(/[-\w$.+!*'(),%;:@&=]*)*(\?[-\w$.+!*'(),%;:@&=]*)?)?

HTTP的完整Java表达式为:

public static Pattern HTTP_PATTERN = Pattern.compile(
    "http://" + "[-0-9a-zA-Z.]+" // 主机名
    + "(:\\d+)?" // 端口
    + "(" // 可选的路径和查询 - 开始
    + "/[-\\w$.+!*'(),%;:@&=]*" // 第一层路径
    + "(/[-\\w$.+!*'(),%;:@&=]*)*" // 可选的其他层路径
    + "(\\?[-\\w$.+!*'(),%;:@&=]*)?" // 可选的查询字符串
    + ")?"); // 可选的路径和查询 - 结束

IP地址

IP地址格式如下:

192.168.112.110

点号分隔,4段数字,每个数字范围是0到255。最简单的表达式为:

(\d{1,3}\.){3}\d{1-3}

\d{1,3}太简单,没有满足0到255之间的约束,要满足这个约束,就要分多种情况考虑。

值是1位数,前面可能有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]

所以,\d{1,3}更为精确的表示为:

(0{0,2}[0-9]|0?[0-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])

所以,加上左右边界环视,IP地址的完整Java表示为:

public 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-5])"
     + "(?![0-9])"); // 右边不能有数字     
相关标签: 正则