搜尋
首頁Javajava教程電腦程式的思考邏輯 (89) - 正規表示式 (中)

電腦程式的思考邏輯 (89) - 正規表示式 (中)

Jun 26, 2017 am 10:01 AM
思維正規則程式電腦邏輯

上节介绍了正则表达式的语法,本节介绍相关的Java API。

正则表达式相关的类位于包java.util.regex下,有两个主要的类,一个是Pattern,另一个是Matcher。Pattern表示正则表达式对象,它与要处理的具体字符串无关。Matcher表示一个匹配,它将正则表达式应用于一个具体字符串,通过它对字符串进行处理。

字符串类String也是一个重要的类,我们在29节专门介绍过String,其中提到,它有一些方法,接受的参数不是普通的字符串,而是正则表达式。此外,正则表达式在Java中是需要先以字符串形式表示的。

下面,我们先来介绍如何表示正则表达式,然后探讨如何利用它实现一些常见的文本处理任务,包括切分、验证、查找、和替换。

表示正则表达式

转义符 '\'

正则表达式由元字符和普通字符组成,字符'\'是一个元字符,要在正则表达式中表示'\'本身,需要使用它转义,即'\\'。

在Java中,没有什么特殊的语法能直接表示正则表达式,需要用字符串表示,而在字符串中,'\'也是一个元字符,为了在字符串中表示正则表达式的'\',就需要使用两个'\',即'\\',而要匹配'\'本身,就需要四个'\',即'\\\\',比如说,如下表达式:

(.*)\1>

对应的字符串表示就是:

"(.*)\\1>"

一个简单规则是,正则表达式中的任何一个'\',在字符串中,需要替换为两个'\'。

Pattern对象

字符串表示的正则表达式可以被编译为一个Pattern对象,比如:

String regex = "(.*)\\1>";
Pattern pattern = Pattern.compile(regex);

Pattern是正则表达式的面向对象表示,所谓编译,简单理解就是将字符串表示为了一个内部结构,这个结构是一个有穷自动机,关于有穷自动机的理论比较深入,我们就不探讨了。

编译有一定的成本,而且Pattern对象只与正则表达式有关,与要处理的具体文本无关,它可以安全地被多线程共享,所以,在使用同一个正则表达式处理多个文本时,应该尽量重用同一个Pattern对象,避免重复编译。

匹配模式

Pattern的compile方法接受一个额外参数,可以指定匹配模式:

public static Pattern compile(String regex, int flags)

上节,我们介绍过三种匹配模式:单行模式(点号模式)、多行模式和大小写无关模式,它们对应的常量分别为:Pattern.DOTALL,Pattern.MULTILINE和Pattern.CASE_INSENSITIVE,多个模式可以一起使用,通过'|'连起来即可,如下所示:

Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL)

还有一个模式Pattern.LITERAL,在此模式下,正则表达式字符串中的元字符将失去特殊含义,被看做普通字符。Pattern有一个静态方法:

public static String quote(String s)

quote()的目的是类似的,它将s中的字符都看作普通字符。我们在上节介绍过\Q和\E,\Q和\E之间的字符会被视为普通字符。quote()基本上就是在字符串s的前后加了\Q和\E,比如,如果s为"\\d{6}",则quote()的返回值就是"\\Q\\d{6}\\E"。

切分

简单情况

文本处理的一个常见需求是根据分隔符切分字符串,比如在处理CSV文件时,按逗号分隔每个字段,这个需求听上去很容易满足,因为String类有如下方法:

public String[] split(String regex)

比如:

String str = "abc,def,hello";
String[] fields = str.split(",");
System.out.println("field num: "+fields.length);
System.out.println(Arrays.toString(fields));

输出为:

field num: 3[abc, def,  hello]

不过,有一些重要的细节,我们需要注意。

转义元字符

split将参数regex看做正则表达式,而不是普通的字符,如果分隔符是元字符,比如. $ | ( ) [ { ^ ? * + \,就需要转义,比如按点号'.'分隔,就需要写为:

String[] fields = str.split("\\.");

如果分隔符是用户指定的,程序事先不知道,可以通过Pattern.quote()将其看做普通字符串。

将多个字符用作分隔符

既然是正则表达式,分隔符就不一定是一个字符,比如,可以将一个或多个空白字符或点号作为分隔符,如下所示:

String str = "abc  def      hello.\n   world";
String[] fields = str.split("[\\s.]+");

fields内容为:

[abc, def, hello, world]

空白字符串

需要说明的是,尾部的空白字符串不会包含在返回的结果数组中,但头部和中间的空白字符串会被包含在内,比如:

String str = ",abc,,def,,";
String[] fields = str.split(",");
System.out.println("field num: "+fields.length);
System.out.println(Arrays.toString(fields));

输出为:

field num: 4[, abc, , def]

找不到分隔符

如果字符串中找不到匹配regex的分隔符,返回数组长度为1,元素为原字符串。

切分数目限制

split方法接受一个额外的参数limit,用于限定切分的数目:

public String[] split(String regex, int limit)

不带limit参数的split,其limit相当于0。关于limit的含义,我们通过一个例子说明下,比如字符串是"a:b:c:",分隔符是":",在limit为不同值的情况下,其返回数组如下表所示:

Pattern的split方法

Pattern也有两个split方法,与String方法的定义类似:

public String[] split(CharSequence input)public String[] split(CharSequence input, int limit)

与String方法的区别是:

  • Pattern接受的参数是CharSequence,更为通用,我们知道String, StringBuilder, StringBuffer, CharBuffer等都实现了该接口;

  • 如果regex长度大于1或包含元字符,String的split方法会先将regex编译为Pattern对象,再调用Pattern的split方法,这时,为避免重复编译,应该优先采用Pattern的方法;

  • 如果regex就是一个字符且不是元字符,String的split方法会采用更为简单高效的实现,所以,这时,应该优先采用String的split方法。

验证

验证就是检验输入文本是否完整匹配预定义的正则表达式,经常用于检验用户的输入是否合法。

String有如下方法:

public boolean matches(String regex)

比如:

String regex = "\\d{8}";
String str = "12345678";
System.out.println(str.matches(regex));

检查输入是否是8位数字,输出为true。

String的matches实际调用的是Pattern的如下方法:

public static boolean matches(String regex, CharSequence input)

这是一个静态方法,它的代码为:

public static boolean matches(String regex, CharSequence input) {
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(input);return m.matches();
}

就是先调用compile编译regex为Pattern对象,再调用Pattern的matcher方法生成一个匹配对象Matcher,Matcher的matches()返回是否完整匹配。

查找

查找就是在文本中寻找匹配正则表达式的子字符串,看个例子:

public static void find(){
    String regex = "\\d{4}-\\d{2}-\\d{2}";
    Pattern pattern = Pattern.compile(regex);
    String str = "today is 2017-06-02, yesterday is 2017-06-01";
    Matcher matcher = pattern.matcher(str);while(matcher.find()){
        System.out.println("find "+matcher.group()+" position: "+matcher.start()+"-"+matcher.end());
    }
}

代码寻找所有类似"2017-06-02"这种格式的日期,输出为:

find 2017-06-02 position: 9-19find 2017-06-01 position: 34-44

Matcher的内部记录有一个位置,起始为0,find()方法从这个位置查找匹配正则表达式的子字符串,找到后,返回true,并更新这个内部位置,匹配到的子字符串信息可以通过如下方法获取:

//匹配到的完整子字符串public String group()//子字符串在整个字符串中的起始位置public int start()//子字符串在整个字符串中的结束位置加1public int end()

group()其实调用的是group(0),表示获取匹配的第0个分组的内容。我们在上节介绍过捕获分组的概念,分组0是一个特殊分组,表示匹配的整个子字符串。除了分组0,Matcher还有如下方法,获取分组的更多信息:

//分组个数public int groupCount()//分组编号为group的内容public String group(int group)//分组命名为name的内容public String group(String name)//分组编号为group的起始位置public int start(int group)//分组编号为group的结束位置加1public int end(int group)

比如:

public static void findGroup() {
    String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
    Pattern pattern = Pattern.compile(regex);
    String str = "today is 2017-06-02, yesterday is 2017-06-01";
    Matcher matcher = pattern.matcher(str);while (matcher.find()) {
        System.out.println("year:" + matcher.group(1)+ ",month:" + matcher.group(2)+ ",day:" + matcher.group(3));
    }
}

输出为:

year:2017,month:06,day:02year:2017,month:06,day:01

替换

replaceAll和replaceFirst

查找到子字符串后,一个常见的后续操作是替换。String有多个替换方法:

public String replace(char oldChar, char newChar)public String replace(CharSequence target, CharSequence replacement)public String replaceAll(String regex, String replacement)public String replaceFirst(String regex, String replacement)

第一个replace方法操作的是单个字符,第二个是CharSequence,它们都是将参数看做普通字符。而replaceAll和replaceFirst则将参数regex看做正则表达式,它们的区别是,replaceAll替换所有找到的子字符串,而replaceFirst则只替换第一个找到的,看个简单的例子,将字符串中的多个连续空白字符替换为一个:

String regex = "\\s+";
String str = "hello    world       good";
System.out.println(str.replaceAll(regex, " "));

输出为:

hello world good

在replaceAll和replaceFirst中,参数replacement也不是被看做普通的字符串,可以使用美元符号加数字的形式,比如$1,引用捕获分组,我们看个例子:

String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
String str = "today is 2017-06-02.";
System.out.println(str.replaceFirst(regex, "$1/$2/$3"));

输出为:

today is 2017/06/02.

这个例子将找到的日期字符串的格式进行了转换。所以,字符'$'在replacement中是元字符,如果需要替换为字符'$'本身,需要使用转义,看个例子:

String regex = "#";
String str = "#this is a test";
System.out.println(str.replaceAll(regex, "\\$"));

如果替换字符串是用户提供的,为避免元字符的的干扰,可以使用Matcher的如下静态方法将其视为普通字符串:

public static String quoteReplacement(String s)

String的replaceAll和replaceFirst调用的其实是Pattern和Matcher中的方法,比如,replaceAll的代码为:

public String replaceAll(String regex, String replacement) {return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

边查找边替换

replaceAll和replaceFirst都定义在Matcher中,除了一次性的替换操作外,Matcher还定义了边查找、边替换的方法:

public Matcher appendReplacement(StringBuffer sb, String replacement)public StringBuffer appendTail(StringBuffer sb)

这两个方法用于和find()一起使用,我们先看个例子:

public static void replaceCat() {
    Pattern p = Pattern.compile("cat");
    Matcher m = p.matcher("one cat, two cat, three cat");
    StringBuffer sb = new StringBuffer();int foundNum = 0;while (m.find()) {
        m.appendReplacement(sb, "dog");
        foundNum++;if (foundNum == 2) {break;
        }
    }
    m.appendTail(sb);
    System.out.println(sb.toString());
}

在这个例子中,我们将前两个"cat"替换为了"dog",其他"cat"不变,输出为:

one dog, two dog, three cat

StringBuffer类型的变量sb存放最终的替换结果,Matcher内部除了有一个查找位置,还有一个append位置,初始为0,当找到一个匹配的子字符串后,appendReplacement()做了三件事情:

  1. 将append位置到当前匹配之前的子字符串append到sb中,在第一次操作中,为"one ",第二次为", two ";

  2. 将替换字符串append到sb中;

  3. 更新append位置为当前匹配之后的位置。

appendTail将append位置之后所有的字符append到sb中。

模板引擎

利用Matcher的这几个方法,我们可以实现一个简单的模板引擎,模板是一个字符串,中间有一些变量,以{name}表示,如下例所示:

String template = "Hi {name}, your code is {code}.";

这里,模板字符串中有两个变量,一个是name,另一个是code。变量的实际值通过Map提供,变量名称对应Map中的键,模板引擎的任务就是接受模板和Map作为参数,返回替换变量后的字符串,示例实现为:

private static Pattern templatePattern = Pattern.compile("\\{(\\w+)\\}");public static String templateEngine(String template, Map<string> params) {
    StringBuffer sb = new StringBuffer();
    Matcher matcher = templatePattern.matcher(template);while (matcher.find()) {
        String key = matcher.group(1);
        Object value = params.get(key);
        matcher.appendReplacement(sb, value != null ?Matcher.quoteReplacement(value.toString()) : "");
    }
    matcher.appendTail(sb);return sb.toString();
}</string>

代码寻找所有的模板变量,正则表达式为:

\{(\w+)\}

'{'是元字符,所以要转义,\w+表示变量名,为便于引用,加了括号,可以通过分组1引用变量名。

使用该模板引擎的示例代码为:

public static void templateDemo() {
    String template = "Hi {name}, your code is {code}.";
    Map params = new HashMap();
    params.put("name", "老马");
    params.put("code", 6789);
    System.out.println(templateEngine(template, params));
}

输出为:

Hi 老马, your code is 6789.

小结

本节介绍了正则表达式相关的主要Java API,讨论了如何在Java中表示正则表达式,如何利用它实现文本的切分、验证、查找和替换,对于替换,我们演示了一个简单的模板引擎。

下一节,我们继续探讨正则表达式,讨论和分析一些常见的正则表达式。

以上是電腦程式的思考邏輯 (89) - 正規表示式 (中)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具