以下筆記全來自韓順平老師的影片:【韩顺平讲 Java】Java 正则表达式专题 - 正则 正则表达式 元字符 限定符 Pattern Matcher 分组 捕获 反向引用等_哔哩哔哩_bilibili

  • 最愛韓順平老師,向他比心!

# 正則表達式簡介

簡介:正則表達式是對字符串報行模式匹配的技術。
英文:regular expression ;簡稱 Regex /regexp
用途:Java 裡的正則表達式可以有效處理文本。
不只有 java,像 javascript, php 等都支持正則表達式。

假設我要從一個中文文本裡取出英文單詞,用傳統的方法的話,就是先看看當前 character 是不是 ASCII 裡 a-z/A-Z 裡的字,如果是,再看下一個 char,一直遍歷下去。缺點是代碼量大,冗長麻煩。 正則表達式就可以用少量代碼處理文本;下面是例子。

# 實例

要從一個文本中找出英文單詞

a
String content = "Java不同于一般的编译语言或解释型语言。它首先将源代码编译成字节码,再依赖各种不同平台上的虚拟机来解释执行字节码,从而具有“一次编写,到处运行”的跨平台特性。在早期JVM中,这在一定程度上降低了Java程序的运行效率。但在J2SE1.4.2发布后,Java的执行速度有了大幅提升。 + 与传统类型不同,Sun公司在推出Java时就将其作为开放的技术。全球的Java开发公司被要求所设计的Java软件必须相互兼容。"
  1. 先創建一個 Pattern 對象,模式對象,可以理解成 一個正則表達式的對象
a
Pattern pattern = Pattern.compile("[a-ZA-Z]+");
// 找 a-z 裡的或 A-Z 裡字,+號為 可以有更多,一止一個單詞
Pattern pattern = Pattern.compile("[0-9]+"); // 找數字而已
Pattern pattern = Pattern.compile("([0-9]+)|([a-ZA-Z]+)"); // 找數字和英語
  1. 創建一個匹配器對象
    • 就是 matcher 匹配器;它按照 pattern,到 content 文本中匹配。
    • 找到就會返回 true,則返回 false
a
Matcher matcher = pattern.matcher(content);
  1. 開始循環匹配
while (matcher.find()){
	// 匹配內容,文本,放到 m.group (0)
	Systerm.out.println("找到: " + m.group(0))
}

# (重要)正則表達式的底層實現

# 案例分析

給你一段字符串的文本,找出所有四個數字連在一起的子串

a
String content ="英国的商业困境因1873至1896年的经济萧条而雪上加霜。"

比如最後返回 1873 1896 1879 1881 1860

# 代碼實現

// 目標:匹配所有四個數字
// 說明:
// 1)   \\d 表示一個任意的數字 
	 Sring regStr = "\\d\\d\\d\\d";
// 2 創造模式對象
	Pattern pattern = Pattern.compile(regStr);
//3 創建匹配器
// 說明:創建匹配器 matcher, 按照正則表達式的規則去匹配 content 字符串
	Matcher matcher = patter.matcher(content);
//4. 開始匹配
while(matcher.find()){
	System.out.println("找到: " + matcher.group(0));
}

# 三個問題

  1. find () 是到底怎麼找
  2. matcher.group 是怎麼被存到裡面
  3. 為甚麼是返回 matcher.group (0) <-- 0,不是 1?

# 分析

# matcher.find () 是怎麼工作,完成的任務是?

  1. 它根據我們指定的規則(通過我的設的 pattern),定位滿足規則的子字符串,比如如果 content 只有 1998
  2. 找到後,將字符串的開始索引記錄到 matcher 對象的 == 屬性 == int[] groups
    • groups[0] = 0 , 把該子字符串的結束 index+1 的值記錄到 groups[1] = 4
    • 也就是它只記錄 1998 裡的開始,和結束的 index
    • 1998 的 1 ,index 為 0;
    • 1998 的 8,index 為 3 但要加 1,所以是 4
  3. 同時記錄 oldLast (裡面還有一個 variable) 的值;為子字符串的結束 index+1 的值;因為他找之後,它下一個再找,就是按照 oldLast 的值來開始找。

# matcher.group (0) 的 0 是甚麼?

  1. 它源碼裡有一段最後返回你要的字符串 return getSubSequence()的function ,它根據 groups[0] = 0groups[1] = 4 的記錄的位置,從 content 開始截取子字符串返回就是 [0,4) 包含 0 但不包含 index 為 4 的位置的值
這個[0,4)是:
String str = "abcdef";
System.out.println(str.substring(0,4)); 
// 記住 0-4 4 是不包含的 所以此 output 是 abcd

# 再循環走一次

假設文本是

1998至2022年

第一次已找到了 1998 了, group[0] = 0 , group[1] = 4
那我找 2022 是怎麼找的呢?

matcher又會重新置0
groups[0] = 520222'是在index = 5的位置
groups[1]92022最後的2''' 是在index=8的為置,然後要+1

如此類推~循環走下去

# matcher.group (1) 又表示甚麼嗎?

這裡涉及到的是分組的概念
分組: (\\d\\d)(\\d\\d)
當表達式有括號時,代表分組;第一個 () 為第一組,第二個 () 括號為第二組。

  1. 它根據我們指定的規則(通過我的設的 pattern),定位滿足規則的子字符串,比如如果 content 是 1998至2022年
  2. 找到後,將字符串的開始索引記錄到 matcher 對象的 == 屬性 == int[] groups
    1. groups[0] = 0 , 把該子字符串的結束 index+1 的值記錄到 groups[1] = 4
    2. (19)(98) ; 第一組 (19)
      1. 記錄 1 組()匹配到的字符串 groups[2] = 0 groups[3] = 2
      2. (19)(98) ; 第二組 (98)
        1. 記錄 2 組()匹配到的字符串, groups[4] = 2 groups[5] = 4
      3. 如果有更多分組... 就繼續記

所以如果 matcher.group (0); 返回的都是整體,整個匹配到 pattern 的值
matcher.group (1) 返回的是第一組
matcher.group (2) 返回的是第二組 以此類推

若你沒有第三組,你要取第三組的話,會報錯 IndexOutOfBoundsException
因為沒有 groups[6] group[7]


# 正則表達式語法

如果要靈活使用正則表達式,必須了解其中各元字符的功能,元字符從功能上大致分為:

  1. 限定符 (限定它出現的次數)
  2. 選擇匹配符
  3. 分組組合和反向引用符
  4. 特殊字符
  5. 字符匹配符
  6. 定位符

以下先說明另一個重要符號 \

# 元字符(Metacharacter) - 轉義號 \

\\ 符號說明: 當我們使用正則表達式去檢索某些特殊字符的時候,需要用到 \\ ,否則檢索不到結果或報錯。

# Example of \

String conect = "abc$(abc(123"
// 匹配 (
String regStr = "\\(" // <- 要加 \\, 否則報錯
Pattern p = Pattern.compile(regStr);
Matcher m = p.m(content);

在 Java 的 regex 裡,\ 代表一個 \

其他需要用到轉義符的字符有:

.*()%^/\?[]{}+

# 元字符 - 字符匹配符

符號符號示例解釋
[]可接收的字符列表[efgh]e、f、g、h 中的任意 1 個字符
[^]不接收的字符列表[^abc]除了 abc 之外的任意 1 個字符,包括數字和特殊符號
-連字符A-Z任意單個大寫字母

符號含義示例解釋匹配輸入
.匹配除 \n 以外的任何字符;包括空格a..b以 a 開頭,b 結尾,中間包括 2 個任意字符的長度為 4 的字符串aaab、aefb、a35b、a#b
\\d匹配單個數字字符,相當於 [0-9]\\d{3}(\\d)?\\d{3} = \\d\\d\\d ;?=3 位數後可能是有數字 / 沒有數字包含 3 個或 4 個數字的字符串123、9876
\\D匹配單個非數字字符,相當於 [^0-9]\\D(\\d)*以單個非數字字符開頭,後接任意個數字字符串a、A323
\\w匹配單個數字、大小寫字母字符,相當於 [0-9a-zA-Z]\\d{3}\\w{4}以 3 個數字字符開頭的長度為 7 的數字字母字符串234ab_d、12345Pe; 注意 @不會出現
\\W匹配單個非數字、大小寫字符,相當於 [^0-9a-zA-Z]\\W+\\d{2}以至少 1 個非數字字母字符開頭,2 個數字字符結尾的字符串#29、#?@10
\\s匹配任何空白字符(空格,制表符等 tab)
\\S匹配任何非空白字符

# Java 在默認時是區分大小寫的

  1. 那不區分大小寫是: (?i)
    比如要區分 abcABC -> (?i)abc 那就不區分了
  2. a ((?i) b) c : 只有 b 不區分大小寫
  3. Pattern p = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); 創建對象時就先指定匹配時不區分

# 元字符 - 選擇匹配符

在匹配某字符串時是選擇性的,即:既可以匹配這,又可匹配那個,這時候你需要用 |

符號含義示例解釋
|匹配 "|" 之前或之後的表達式ab|cdab 或者 cd

# 限定符

用於指定某前面的字符和組合項連續出現多少次

# 定位符

定位符,規定要匹配的字符串出現的位置,比如在字符串的開始還是在結束的位置,這個相當有用必須掌握

符號符號示例解釋匹配輸入
^指定起始字符^[0-9]+[a-z]*以至少 1 個數字開頭,後接任意個小寫字母的字符串123、6aa、55edf
$指定結束字符^[0-9]\\-[a-z]+$以一個數字開始後接連字符 "-",並以至少 1 個小寫字母結尼的字符串1-a
\\b匹配目標字符串的邊界han\\b這裡說的字符串的邊界是指子串間有空格,或者是目標字符串的結束位置hanshunpingsphan nnhan
\B匹配目標字符串的非邊界han\\B和 \b 的含義剛剛相反hanshuningsphan nnhan

# 分組

常用分組構造形式說明
(pattern)非命名捕獲。捕獲匹配的子字符串。編號為零的第一個捕獲是由整個正則表達式模式匹配的文本,其他捕獲結果則根據左括號的順序從 1 開始自動編號
(?<name>pattern)命名捕獲。將匹配的子字符串捕獲到一個組名稱或者編號名稱中。用於 name 的字符串不能包含任何標點符號,並且不能以數字開頭,可以使用單引號替代尖括號,比如 (?'name' )

# example :非命名分組

String content = "abcdABCDs7789 n1122";
// 非命名方式來分組
String regStr = "(\\d\\d)(\\d)(\\d)";
//1. matcher.group (0) 得到匹配到的字符串
//2. matcher.group (1) 得到匹配到的字符串的第 1 個分組內容 
//3. matcher.group (2) 得到匹配到的字符串的第 2 個分組內容 
// 如此類推
Pattern pattern = Patter.compile(regStr)
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
	System.out.println("找到:" + matcher.group(0)); //7789
	System.out.println("第1個分組內容:" + matcher.group(1)); //77
	System.out.println("第2個分組內容:" + matcher.group(2)); //8
	System.out.println("第2個分組內容:" + matcher.group(3)); //9
}

# example: 命名分組

即可以給分組取名

a
String content = "abcdABCDs7789 n1122";
// 非命名方式來分組
String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";
//1. matcher.group (0) 得到匹配到的字符串
//2. matcher.group (1) 得到匹配到的字符串的第 1 個分組內容 
//3. matcher.group (2) 得到匹配到的字符串的第 2 個分組內容 
// 如此類推
Pattern pattern = Patter.compile(regStr)
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
	System.out.println("找到:" + matcher.group(0)); //7789
	System.out.println("第1個分組內容[通過組名]:" + matcher.group("g1")); //77
	System.out.println("第2個分組內容[通過組名]:" + matcher.group("g2"); //89
}

# 特別分組

  1. (?:pattern) - 非捕獲分組:
    • 用法: String regStr ="Tiffany(?:是好學生|失業中|努力找工作啦)"
      • 它看上去是分組,但並不會捕獲, group[1]並不會有的
  2. (?=pattern) - 非捕獲分組,不能使用 group (1)
a
String content ="Tiffany是好學生,Tiffany是壞小孩,Tiffany努力找工作啦"
String regStr = "Tiffany(?=是好學生|努力找工作啦)"
// 這個 pattern 意思是:
// 在 "Tiffany 是好學生或者 Tiffany 努力找工作啦" 裡面找 tiffany
while (matcher.find()){
	System.out.println("找到:" + matcher.group(0)); 
}
//output:
// 找到:Tiffany 
// 找到:Tiffany
//
//Tiffany 是壞小孩沒有被查找
  1. (?!pattern) 非捕獲分組,且取反!
a
String content ="Tiffany是好學生,Tiffany是壞小孩,Tiffany努力找工作啦"
String regStr = "Tiffany(?!是好學生|努力找工作啦)"
/**
最後出輸的是:
Tiffany 
是從 Tiffany 是壞小孩裡找到
**/

另外的符號:

String content = "hello1111";
String regStr = "\\d+";// 默認是貪婪匹配 返回 1111
String regStr2 = "\\d+?"// 非貪婪匹配,返回 1

# 正則表達式三個常用類

java.util.regex 包主要包括以下三個類:

# 1. Pattern 類

- 是一個正則表達式的對象。Pattern類沒有公共構造 方法。要創建一個pattern對象,調用其公共靜態方法,它返回一個pattern對象。該方法接受一個正則表達式作為它的第一個參數,比如 `Pattern r = Pattern.compile(pattern);`
- 返回true | false

# 實例

用於整體匹配,在驗證輸入字符串是否滿足條件使用

String content = "hello tiffany hello ladies";
String regStr = "hello"; // 用這個會錯,因為這是匹配其中一部分
String regStr2 = "hello.*" //hello 之後的任意字符
boolean ismatch = Pattern.matches(regStr2, content);
System.out.println("整體匹配= " + ismatch); 
// 返回 true

# 2. Matcher 類

- Matcher對象是對輸入字符串進行解釋和匹配的引擎。與Patter類一樣,也沒有公共構造方法,你需要調用Pattern對象的matcher方法來獲得一個Matcher對象

# 實例

String content = "hello tiffany hello ladies";
String regStr = "hello"; // 用這個會錯,因為這是匹配其中一
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
	System.out.print(==================);
	System.out.print(matcher.start());
	System.out.print(matcher.end());
	System.out.print("找到: " +
					 content.substring(matcher.start(), matcher.end()));
}
//output
==================
0
5
找到:hello
==================
14
19
找到:hello

Matcher的其他方法

# replaceAll 實例

a
String content = "abc先生你好";
String regStr = "abc"
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
// 返回的字符串,才是替換後的字符串,它不會更改原來的 content
String newContent = matcher.replaceAll("Bob");

# 3. PatternSyntaxException

- PatternSyntaxException是一個非強制異常類,它表示一個正則表達式模式中的語法錯誤。

# 分組、捕獲、反向引用

需求:

  • 給你一段文本,請你找出所有四個數字連在一起的子串,並且這四個數字要滿足:
    1. 第一位和第四位相同
    2. 第二位與第三位相同
  • 比如: 1221 、5775

##Example

1. 要匹配兩個連續的相同數字 : (\\d)\\1 <--\\1指的是反向引用一次
2. 要匹配五個連續的相同數字:(\\d)\\1{4} <-引用一次,再出現4個所引用的
也就等於(\\d)\\1\\1\\1\\1
4. 要匹配個數與千位相同,十位與百位相同的數
比如: 52251551
可以寫成: (\\d)(\\d)\\2\\1    
        // 用 2 表達第二個 (\\d)  用 1 引用千位的 (\\d)

Example 2

String content = "hello48 tiffany1221 hello1234 ladies";
String regStr = "(\\d)(\\d)\\2\\1"; 
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
	System.out.print("找到: " + matcher.group(0);
}

Example 3
請在字符串中檢索商品編號,如:1231-333999111 這樣的號碼

  • 要滿足前面是一個五位數,然後一個 - , 然後一個九位數,連續的每三位要相同
String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";

# 結巴程序

把類似: 我...我要...學學學學...編程java!
通過正則表達式修改成 "我要學編程 java"

a
String content ="我...我要...學學學學...編程java"
// 1. 把。去掉
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(content);
// 返回一開始的 content
content = matcher.replaceAll(""); // 把點換成空
//content = " 我我要學學學學編程
//2, 去掉重覆的字
// 不管我當前字是甚麼,我都匹配後面的,看是不是重覆
// 使用 (.)\\1+ <--- . 是任意字的反向引用,有沒有重覆 + 是 1 到多
Pattern pattern = Pattern.compile("(.)\\1+");
// 重設 matcher
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
	System.out.print("找到: " + matcher.group(0); 
/**
輸出:
找到: 我我 
找到: 學學學學
**/
}
//3. 使用反向引用 $1 來替換匹配到的內容
content = matcher.replaceAll("$1"); //<-- $1 就是第一個反向引用的內容					 
/** 即剛剛的 我我    學學學學
					 **/

第二個方法

content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");

# String 類中使用正則表達式

String content ="新的JDK1.3, JDK1.4";

比如說我要把 JDK1.3 和 JDK1.4 替換成 JDK;

content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK");

# 判斷功能

a
Stringpublic boolean matches(String regex){}
  • 要求驗證一個手機號,要求必須是 138 或 139 開頭
String content = "13688888888";
if(content.matches("1(38|39)\\d{8}")){
	System.out.println("驗證成功")
} else {
	System.out.println("驗證失敗")
}

# 分割功能

String 類的 public String[] split(String regex)

按照 #或者 - 或者~或者數字來分割

String content ="hello#abc-jack12smith~中國"
String[] split = content.split("#|~|~|\\d+");
for (String s : split){
	System.out.println(s);
}

# 練習

# 2840 · Simple string matching and replacement

URL: 2840 · Simple string matching and replacement - LintCode

# Description

Given a string, there are strange characters in that string that are not easy to read. Now you are asked to replace the strange characters in it with spaces, you just need to write replaceString method in Solution class to return the replaced string.

Input.

You *can~ go to /+as#far!as^, you want to; go to.

Output.

You can go as far as , you want to go.

# Solution

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Solution {
    public static String replaceString (String str, String replacement) {
        String content = str;
      
        Pattern pattern = Pattern.compile("[\\Q/|~!@#$%^&*();:_+-[]\\E]+"); 
        Matcher matcher = pattern.matcher(content);
        content = matcher.replaceAll(replacement);
        return content;
    }
}

# 😃

# 2832 · A simple check of the mailbox format

# Description

Please use regular expressions (of course you don't have to use them) to do a simple check of the mailbox format and return the result.

  • The first letter of the mailbox is a letter or underscore other than a number
  • Mailboxes must contain @ and .com characters
  • Mailboxes can have multiple levels of domain names
  • The mailbox name part also has multiple levelsvels

# Sample example one.

lintcode@chapter.com

Output:

true

# Solution

public class Solution {
    public static boolean isMatch(String s) {
      return s.matches("^[a-zA-Z_][\\w]*[.\\w]*@[\\w.]+.com(\\.[.a-z]+)*$");
        /**
        例子:_lint123.123code123.co123de@chapter123.com.cn
        ^[a-zA-Z_] 第一個字是英文字母,非數字,可以有下劃線
        [\\w]*     第二個字符開始可以是任意字,包括數字,可以是 0 到多個
        [.\\w]*   之後的 pattern 是從。開始,是任意字,包括數字,可以是 0 到多個
        @    是固定要有的
        [\\w.]+  在 @之後的字,一定要至少有一個字符,且最後會帶.
        (\\.[.a-z]+)*$    1. 括號代表 pattern, 在括號裡的。要用 \\ 轉義符 -->  (\\.)
                          2. 中括號 [] 裡的。不需要用轉義符 \\
                          3.* 號代表,這個 pattern 可以沒有,也可以出現任意次
                          4.$ 代表,要以前面的那個子符 或者 表達式 為結尾。
        **/
    }
}