国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
(89) 正則表達(dá)式 (中) / 計(jì)算機(jī)程序的思維邏輯

上節(jié)介紹了正則表達(dá)式的語法,本節(jié)介紹相關(guān)的Java API。


正則表達(dá)式相關(guān)的類位于包java.util.regex下,有兩個(gè)主要的類,一個(gè)是Pattern,另一個(gè)是Matcher。Pattern表示正則表達(dá)式對(duì)象,它與要處理的具體字符串無關(guān)。Matcher表示一個(gè)匹配,它將正則表達(dá)式應(yīng)用于一個(gè)具體字符串,通過它對(duì)字符串進(jìn)行處理。


字符串類String也是一個(gè)重要的類,我們?cè)?9節(jié)專門介紹過String,其中提到,它有一些方法,接受的參數(shù)不是普通的字符串,而是正則表達(dá)式。此外,正則表達(dá)式在Java中是需要先以字符串形式表示的。


下面,我們先來介紹如何表示正則表達(dá)式,然后探討如何利用它實(shí)現(xiàn)一些常見的文本處理任務(wù),包括切分、驗(yàn)證、查找、和替換。


表示正則表達(dá)式

轉(zhuǎn)義符 ''\''

正則表達(dá)式由元字符和普通字符組成,字符''\''是一個(gè)元字符,要在正則表達(dá)式中表示''\''本身,需要使用它轉(zhuǎn)義,即''\\''。


在Java中,沒有什么特殊的語法能直接表示正則表達(dá)式,需要用字符串表示,而在字符串中,''\''也是一個(gè)元字符,為了在字符串中表示正則表達(dá)式的''\'',就需要使用兩個(gè)''\'',即''\\'',而要匹配''\''本身,就需要四個(gè)''\'',即''\\\\'',比如說,如下表達(dá)式:

<(\w+)>(.*)


對(duì)應(yīng)的字符串表示就是:

''<(\\w+)>(.*)''


一個(gè)簡(jiǎn)單規(guī)則是,正則表達(dá)式中的任何一個(gè)''\'',在字符串中,需要替換為兩個(gè)''\''。


Pattern對(duì)象

字符串表示的正則表達(dá)式可以被編譯為一個(gè)Pattern對(duì)象,比如:

String regex = ''<(\\w+)>(.*)'';
Pattern pattern = Pattern.compile(regex);


Pattern是正則表達(dá)式的面向?qū)ο蟊硎荆^編譯,簡(jiǎn)單理解就是將字符串表示為了一個(gè)內(nèi)部結(jié)構(gòu),這個(gè)結(jié)構(gòu)是一個(gè)有窮自動(dòng)機(jī),關(guān)于有窮自動(dòng)機(jī)的理論比較深入,我們就不探討了。


編譯有一定的成本,而且Pattern對(duì)象只與正則表達(dá)式有關(guān),與要處理的具體文本無關(guān),它可以安全地被多線程共享,所以,在使用同一個(gè)正則表達(dá)式處理多個(gè)文本時(shí),應(yīng)該盡量重用同一個(gè)Pattern對(duì)象,避免重復(fù)編譯。


匹配模式

Pattern的compile方法接受一個(gè)額外參數(shù),可以指定匹配模式:

public static Pattern compile(String regex, int flags)


上節(jié),我們介紹過三種匹配模式:?jiǎn)涡心J?點(diǎn)號(hào)模式)、多行模式和大小寫無關(guān)模式,它們對(duì)應(yīng)的常量分別為:Pattern.DOTALL,Pattern.MULTILINE和Pattern.CASE_INSENSITIVE,多個(gè)模式可以一起使用,通過''|''連起來即可,如下所示:

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


還有一個(gè)模式Pattern.LITERAL,在此模式下,正則表達(dá)式字符串中的元字符將失去特殊含義,被看做普通字符。Pattern有一個(gè)靜態(tài)方法:

public static String quote(String s)


quote()的目的是類似的,它將s中的字符都看作普通字符。我們?cè)谏瞎?jié)介紹過\Q和\E,\Q和\E之間的字符會(huì)被視為普通字符。quote()基本上就是在字符串s的前后加了\Q和\E,比如,如果s為''\\d{6}'',則quote()的返回值就是''\\Q\\d{6}\\E''。


切分

簡(jiǎn)單情況

文本處理的一個(gè)常見需求是根據(jù)分隔符切分字符串,比如在處理CSV文件時(shí),按逗號(hào)分隔每個(gè)字段,這個(gè)需求聽上去很容易滿足,因?yàn)镾tring類有如下方法:

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]


不過,有一些重要的細(xì)節(jié),我們需要注意。


轉(zhuǎn)義元字符

split將參數(shù)regex看做正則表達(dá)式,而不是普通的字符,如果分隔符是元字符,比如. $ | ( ) [ { ^ ? * + \,就需要轉(zhuǎn)義,比如按點(diǎn)號(hào)''.''分隔,就需要寫為:

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


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


將多個(gè)字符用作分隔符

既然是正則表達(dá)式,分隔符就不一定是一個(gè)字符,比如,可以將一個(gè)或多個(gè)空白字符或點(diǎn)號(hào)作為分隔符,如下所示:

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


fields內(nèi)容為:

[abc, def, hello, world]


空白字符串

需要說明的是,尾部的空白字符串不會(huì)包含在返回的結(jié)果數(shù)組中,但頭部和中間的空白字符串會(huì)被包含在內(nèi),比如:

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的分隔符,返回?cái)?shù)組長(zhǎng)度為1,元素為原字符串。


切分?jǐn)?shù)目限制

split方法接受一個(gè)額外的參數(shù)limit,用于限定切分的數(shù)目:

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


不帶limit參數(shù)的split,其limit相當(dāng)于0。關(guān)于limit的含義,我們通過一個(gè)例子說明下,比如字符串是''a:b:c:'',分隔符是'':'',在limit為不同值的情況下,其返回?cái)?shù)組如下表所示:

Pattern的split方法

Pattern也有兩個(gè)split方法,與String方法的定義類似:

public String[] split(CharSequence input)

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


與String方法的區(qū)別是:

  • Pattern接受的參數(shù)是CharSequence,更為通用,我們知道String, StringBuilder, StringBuffer, CharBuffer等都實(shí)現(xiàn)了該接口;

  • 如果regex長(zhǎng)度大于1或包含元字符,String的split方法會(huì)先將regex編譯為Pattern對(duì)象,再調(diào)用Pattern的split方法,這時(shí),為避免重復(fù)編譯,應(yīng)該優(yōu)先采用Pattern的方法;

  • 如果regex就是一個(gè)字符且不是元字符,String的split方法會(huì)采用更為簡(jiǎn)單高效的實(shí)現(xiàn),所以,這時(shí),應(yīng)該優(yōu)先采用String的split方法。


驗(yàn)證

驗(yàn)證就是檢驗(yàn)輸入文本是否完整匹配預(yù)定義的正則表達(dá)式,經(jīng)常用于檢驗(yàn)用戶的輸入是否合法。


String有如下方法:

public boolean matches(String regex)


比如:

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


檢查輸入是否是8位數(shù)字,輸出為true。


String的matches實(shí)際調(diào)用的是Pattern的如下方法:

public static boolean matches(String regex, CharSequence input)


這是一個(gè)靜態(tài)方法,它的代碼為:

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


就是先調(diào)用compile編譯regex為Pattern對(duì)象,再調(diào)用Pattern的matcher方法生成一個(gè)匹配對(duì)象Matcher,Matcher的matches()返回是否完整匹配。


查找

查找就是在文本中尋找匹配正則表達(dá)式的子字符串,看個(gè)例子:

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-19
find 2017-06-01 position: 34-44


Matcher的內(nèi)部記錄有一個(gè)位置,起始為0,find()方法從這個(gè)位置查找匹配正則表達(dá)式的子字符串,找到后,返回true,并更新這個(gè)內(nèi)部位置,匹配到的子字符串信息可以通過如下方法獲?。?/p>

//匹配到的完整子字符串

public String group()

//子字符串在整個(gè)字符串中的起始位置

public int start()

//子字符串在整個(gè)字符串中的結(jié)束位置加1

public int end()


group()其實(shí)調(diào)用的是group(0),表示獲取匹配的第0個(gè)分組的內(nèi)容。我們?cè)谏瞎?jié)介紹過捕獲分組的概念,分組0是一個(gè)特殊分組,表示匹配的整個(gè)子字符串。除了分組0,Matcher還有如下方法,獲取分組的更多信息:

//分組個(gè)數(shù)

public int groupCount()

//分組編號(hào)為group的內(nèi)容

public String group(int group)
//分組命名為name的內(nèi)容

public String group(String name)

//分組編號(hào)為group的起始位置

public int start(int group)

//分組編號(hào)為group的結(jié)束位置加1

public 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:02
year:2017,month:06,day:01


替換

replaceAll和replaceFirst

查找到子字符串后,一個(gè)常見的后續(xù)操作是替換。String有多個(gè)替換方法:

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)


第一個(gè)replace方法操作的是單個(gè)字符,第二個(gè)是CharSequence,它們都是將參數(shù)看做普通字符。而replaceAll和replaceFirst則將參數(shù)regex看做正則表達(dá)式,它們的區(qū)別是,replaceAll替換所有找到的子字符串,而replaceFirst則只替換第一個(gè)找到的,看個(gè)簡(jiǎn)單的例子,將字符串中的多個(gè)連續(xù)空白字符替換為一個(gè):

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


輸出為:

hello world good


在replaceAll和replaceFirst中,參數(shù)replacement也不是被看做普通的字符串,可以使用美元符號(hào)加數(shù)字的形式,比如$1,引用捕獲分組,我們看個(gè)例子:

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.


這個(gè)例子將找到的日期字符串的格式進(jìn)行了轉(zhuǎn)換。所以,字符''$''在replacement中是元字符,如果需要替換為字符''$''本身,需要使用轉(zhuǎn)義,看個(gè)例子:

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


如果替換字符串是用戶提供的,為避免元字符的的干擾,可以使用Matcher的如下靜態(tài)方法將其視為普通字符串:

public static String quoteReplacement(String s)


String的replaceAll和replaceFirst調(diào)用的其實(shí)是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)


這兩個(gè)方法用于和find()一起使用,我們先看個(gè)例子:

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


在這個(gè)例子中,我們將前兩個(gè)''cat''替換為了''dog'',其他''cat''不變,輸出為:

one dog, two dog, three cat


StringBuffer類型的變量sb存放最終的替換結(jié)果,Matcher內(nèi)部除了有一個(gè)查找位置,還有一個(gè)append位置,初始為0,當(dāng)找到一個(gè)匹配的子字符串后,appendReplacement()做了三件事情:

  1. 將append位置到當(dāng)前匹配之前的子字符串a(chǎn)ppend到sb中,在第一次操作中,為''one '',第二次為'', two '';

  2. 將替換字符串a(chǎn)ppend到sb中;

  3. 更新append位置為當(dāng)前匹配之后的位置。


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


模板引擎

利用Matcher的這幾個(gè)方法,我們可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的模板引擎,模板是一個(gè)字符串,中間有一些變量,以{name}表示,如下例所示:

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


這里,模板字符串中有兩個(gè)變量,一個(gè)是name,另一個(gè)是code。變量的實(shí)際值通過Map提供,變量名稱對(duì)應(yīng)Map中的鍵,模板引擎的任務(wù)就是接受模板和Map作為參數(shù),返回替換變量后的字符串,示例實(shí)現(xiàn)為:

private static Pattern templatePattern = Pattern.compile(''\\{(\\w+)\\}'');

public static String templateEngine(String template, Map 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();
}


代碼尋找所有的模板變量,正則表達(dá)式為:

\{(\w+)\}

''{''是元字符,所以要轉(zhuǎn)義,\w+表示變量名,為便于引用,加了括號(hào),可以通過分組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.


小結(jié)

本節(jié)介紹了正則表達(dá)式相關(guān)的主要Java API,討論了如何在Java中表示正則表達(dá)式,如何利用它實(shí)現(xiàn)文本的切分、驗(yàn)證、查找和替換,對(duì)于替換,我們演示了一個(gè)簡(jiǎn)單的模板引擎。


下一節(jié),我們繼續(xù)探討正則表達(dá)式,討論和分析一些常見的正則表達(dá)式。


(與其他章節(jié)一樣,本節(jié)所有代碼位于 https://github.com/swiftma/program-logic,位于包shuo.laoma.dynamic.c89下)



本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
JAVA與正則表達(dá)式
Java過濾特殊字符的正則表達(dá)式 - Java - JavaEye論壇
Java正則表達(dá)式典型用例
過濾特殊字符
JAVA中正則表達(dá)式匹配,替換,查找,切割的方法
java 正則表達(dá)式的使用
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服