電腦程式的思維邏輯 (90) - 正則表達式 (下 - 剖析常見表達式)

来源:http://www.cnblogs.com/swiftma/archive/2017/06/22/7062987.html
-Advertisement-
Play Games

本節詳細討論和分析一些常見的正則表達式,包括郵編、日期和時間、手機和固定電話、身份證、Email地址、IP地址、URL和中文字元。 ...


​88節介紹了正則表達式的語法,上節介紹了正則表達式相關的Java API,本節來討論和分析一些常用的正則表達式,具體包括:

  • 郵編
  • 電話號碼,包括手機號碼和固定電話號碼
  • 日期和時間
  • 身份證
  • IP地址
  • URL
  • Email地址
  • 中文字元

對於同一個目的,正則表達式往往有多種寫法,大多沒有唯一正確的寫法,本節的寫法主要是示例。此外,寫一個正則表達式,匹配希望匹配的內容往往比較容易,但讓它不匹配不希望匹配的內容,則往往比較困難,也就是說,保證精確性經常是很難的,不過,很多時候,我們也沒有必要寫完全精確的表達式,需要寫到多精確與你需要處理的文本和需求有關,另外,正則表達式難以表達的,可以通過寫程式進一步處理。這麼描述可能比較抽象,下麵,我們會具體討論分析。

郵編

郵編比較簡單,就是6位數字,首位不能是0,所以表達式可以為:

[1-9][0-9]{5}

 這個表達式可以用於驗證輸入是否為郵編,比如:

public static Pattern ZIP_CODE_PATTERN = Pattern.compile(
        "[1-9][0-9]{5}");

public static boolean isZipCode(String text) {
    return ZIP_CODE_PATTERN.matcher(text).matches();
}

但如果用於查找,這個表達式是不夠的,看個例子:

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");
}

文本中只有一個郵編,但輸出卻為:

100013
186123

這怎麼辦呢?可以使用88節介紹的環視邊界匹配,對於左邊界,它前面的字元不能是數字,環視表達式為:

(?<![0-9])

對於右邊界,它右邊的字元不能是數字,環視表達式為:

(?![0-9])

所以,完整的表達式可以為:

(?<![0-9])[1-9][0-9]{5}(?![0-9])

使用這個表達式,也就是說,將ZIP_CODE_PATTERN改為:

public static Pattern ZIP_CODE_PATTERN = Pattern.compile(
        "(?<![0-9])" // 左邊不能有數字
        + "[1-9][0-9]{5}"
        + "(?![0-9])"); // 右邊不能有數字

就可以輸出期望的結果了。

非0開頭的6位數字就一定是郵編嗎?答案當然是否定的,所以,這個表達式也不是精確的,如果需要更精確的驗證,可以寫程式進一步檢查。

手機號碼

中國的手機號碼都是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])"); // 右邊不能有數字

日期

日期的表示方式有很多種,我們只看一種,形如:

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

時間

考慮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表示為:

public static Pattern TIME_PATTERN = Pattern.compile(
        "(?<![0-9])" // 左邊不能有數字
        + "([0-1][0-9]|2[0-3])" // 小時
        + ":" + "[0-5][0-9]"// 分鐘
        + "(?![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])"); // 右邊不能有數字

符合這個要求的就一定是身份證號碼嗎?當然不是,身份證還有一些更為具體的要求,本文就不探討了。

IP地址

IP地址格式如下:

192.168.3.5 

點號分隔,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])"); // 右邊不能有數字     

URL

URL的格式比較複雜,其規範定義在https://tools.ietf.org/html/rfc1738,我們只考慮http協議,其通用格式是:

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

開始是http://,接著是主機名,主機名之後是可選的埠,再之後是可選的路徑,路徑後是可選的查詢字元串,以?開頭。

一些例子:

http://www.example.com
http://www.example.com/ab/c/def.html
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]*)?)?

精確表達式為:

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

HTTP的完整Java表達式為:

public static Pattern HTTP_PATTERN = Pattern.compile(
        "http://" + "[-0-9a-zA-Z.]+" // 主機名
        + "(:\\d+)?" //
        + "(" // 可選的路徑和查詢 - 開始
            + "/[-\\w$.+!*'(),%;:@&=]*" // 第一層路徑
            + "(/[-\\w$.+!*'(),%;:@&=]*)*" // 可選的其他層路徑
            + "(\\?[-\\w$.+!*'(),%;:@&=]*)?" // 可選的查詢字元串
        + ")?"); // 可選的路徑和查詢 - 結束 

Email地址

完整的Email規範比較複雜,定義在https://tools.ietf.org/html/rfc822,我們先看一些實際中常用的。

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

[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郵箱,它對於用戶名的要求為:

  • 3-18字元,可使用英文、數字、減號、點或下劃線
  • 必須以英文字母開頭,必須以英文字母或數字結尾
  • 點、減號、下劃線不能連續出現兩次或兩次以上

如果只有第一條,可以為:

[-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位,不超過64位
  • 開頭不能是減號、點號和下劃線

比如:

[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}"); // 頂級功能變數名稱      

中文字元

中文字元的Unicode編號一般位於\u4e00和\u9fff之間,所以匹配任意一個中文字元的表達式可以為:

[\u4e00-\u9fff]

Java表達式為:

public static Pattern CHINESE_PATTERN = Pattern.compile(
        "[\\u4e00-\\u9fff]");

小結

本節詳細討論和分析了一些常見的正則表達式,在實際開發中,有些可以直接使用,有些需要根據具體文本和需求進行調整。

至此,關於正則表達式,我們就介紹完了,相信你對正則表達式一定有了一個更為清晰透徹的理解!

在之前的章節中,我們都是基於Java 7討論的,從下節開始,我們探討Java 8的一些特性,尤其是函數式編程。

(與其他章節一樣,本節所有代碼位於 https://github.com/swiftma/program-logic,位於包shuo.laoma.regex.c90下)

----------------

未完待續,查看最新文章,敬請關註微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及電腦技術的本質。用心原創,保留所有版權。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文目錄: 12.1 配置定時任務 12.2 crontab file 12.3 crond命令的調試 12.4 精確到秒的任務計劃 12.1 配置定時任務 首先需弄清的概念: (1).crond是一個daemon類程式,路徑為/usr/sbin/crond。預設會以後臺方式啟動,service或s ...
  • 本文目錄: 11.1 服務的概念 11.2 管理獨立守護進程 11.3 管理服務的開機自啟動 11.4 管理xinetd及相關瞬時守護進程 11.5 CentOS 7上管理服務 CentOS 7和CentOS 6管理服務的方式完全不同。本文先說明CentOS 6上的管理方式,在最後列出CentOS ...
  • 說明:為表述方便,待填的表為【表A】,資料庫的表稱為【表B】。該工具可以快捷地從【表B】中提取相關數據到【表A】,順序和列可自定義。 使用方法:1、打開【ExcelGetFromB.exe】(如要打開示例則根據文字提示跳過步驟2)2、打開【表A】和【表B】(如果打開2個以上Excel表,需要在【設置 ...
  • 本文目錄: 10.1 /proc的意義及說明 10.2 查看進程信息 10.2.1 pstree命令 10.2.2 ps命令 10.2.3 ps後grep問題 10.2.4 top、htop以及iftop命令 10.3 vmstat命令 10.4 iostat命令 10.5 sar命令 10.5.1 ...
  • 本文目錄: 9.1 進程的簡單說明 9.11 進程和程式的區別 9.12 多任務和cpu時間片 9.13 父子進程及創建進程的方式 9.14 進程的狀態 9.15 舉例分析進程狀態轉換過程 9.16 進程結構和子shell 9.2 job任務 9.3 終端和進程的關係 9.4 信號 9.41 需知道 ...
  • 學習網址:http://c.biancheng.net/cpp/html/2728.html 1.在當前目錄下添加多重文件夾: ...
  • 您有這樣的牢騷麽? 有一周沒更新博客了,簡單說下在乾什麼吧;主要是公司安排對接某旅游大公司的介面,介面數量倒也就10個左右,對接完後還需要加入到業務系統中和App端,因此還是需要花點時間的;時間上來說業務需求安排在6月最後一周上線,整個3周的時間,就本人一人負責,由於在這之前對接過另外一個公司介面, ...
  • 我們接著上一篇文章進行講解 http://www.cnblogs.com/songjianhui/p/7060698.html 一:客戶端通過添加引用調用服務 WCF應用服務被成功寄宿後,WCF服務應用便開始了服務調用請求的監聽工作。此外,服務寄宿將服務描述通過元數據的形式發佈出來,相應的客戶端就可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...