1. 程式人生 > >常見正則表示式剖析

常見正則表示式剖析

沒錯,又是正則,沒辦法,這東西入門很簡單,但真正能寫好,那是真心難,繼續學吧。。。
基本語法我在這裡就不贅述了,需要的話可以關注我公眾號,裡面有很詳細的語法介紹和示例。

今天我們來針對幾個常見的正則來慢慢刨析

電話號碼

這個應該是最常用的,沒有之一了吧

  • 手機號

中國的手機號碼都是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. 開頭不能是減號、點號和下劃線

這個表示式可以為:

[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])"); // 右邊不能有數字