5.JAVA NIO正則表達式

第五章 正則表達式

本章我們將討論新的程序包java.util.regex(見圖 5-1)中類的API(譯註 10)。JSR51,即 Java規範請求(Java Specification Request),定義了新的I/O權能,它還明確了添加到Java平臺 上的正則表達式處理技術。儘管嚴格說來正則表達式並不是I/O,但是它們最常用於瀏覽從文 件或數據流(stream)中讀取的文本數據。

Perl、egrep和其它文本處理工具有着功能強大的模式匹配(pattern matching),本章中你 將學會如何使用新的Java API達到相同的模式匹配效果。關於正則表達式的詳細探究不在本書 的範圍內,但是本書假設您熟悉正則表達式的實際作用。假若你不熟悉正則表達式,想要提升 自己的技能,又或者對本章內容感到困惑,我建議你挑選一本好的參考書。O’Reilly出版了的 一本正則表達式權威書籍(甚至JDK文檔都引用了它):Mastering Regular Expressions,作者 Jeffrey E.F. Friedl(譯註11 )。

1.正則表達式基礎

正則表達式(regular expression)是描述或表達在目標字符序列內匹配一定字符模式的字符序列(譯註 12 )。正則表達式在Unix世界已存在多年並憑藉sed、grep(查找正則表達式)、 awk等文本編輯工具而廣爲使用。在Unix平臺上使用的正則表達式因其長久的歷史已經成了大 部分正則表達式處理器的基礎。開放組(The Open Group)是Unix標準團體,它將正則表達式 句法作爲Unix規範的一部分作了詳述(譯註 13 )。

http://www.opengroup.org/onlinepubs/7908799/xbd/re.html

廣受歡迎的Perl (注 1)腳本語言將正則表達式處理技術直接納入其語言句法的麾下。隨 着Perl的演變及流行,它在添加更多複雜功能的同時也擴展了正則表達式句法。Perl因其能力 及靈活性而幾乎無所不在,並且隨後也建立了正則表達式處理技術的實際標準。

java.util.regex 的類提供的正則表達式權能(capability)效仿了 Perl 5 提供的權能。細微差別主要囿於一般用戶極少接觸的神祕領域。關於 java.util.regex.Pattern 類的細節,詳見 5.4 節, 表 5-7 及 Javadoc API 文檔。

在Perl腳本中,正則表達式被作爲內聯的(inline,譯註 14 )子過程匹配變量值。將正則表 達式賦值(evaluation)集成到語言中使得Perl在文本處理的腳本應用中廣受歡迎。直到現在, Java已經難以企及。因爲在文本文件中處理非平凡模式(notrivial pattern)缺乏內置工作,所 以用Java處理文本文件相當累贅麻煩。

Perl享有較高水平的正則表達式集成,正當Java從未觸及這一高度之際,java.util.regex包爲 Java帶來了福音,它可以提供與Perl同等水平的表達能力。當然,Perl與Java的使用模型是不同的:Perl是過程式腳本語言,而Java是編譯的、面向對象的語言。但是Java正則表達式API易於使用, 並且現在允許Java輕鬆實現文本處理任務, 而該任務在以往都是“外包”給Perl的。

2.Java正則表達式

java.util.regex 程序包只包含用於實現 Java 正則表達式處理技術的兩個類,分別名 爲 Pattern 和 Matcher 。 自 然 而 然 你 會 想 到 正 則 表 達 式 由 模 式 匹 配 ( pattern matching)而成.java.lang 還定義了一個新接口,它支持這些新的類。在研究 Patternt 和 Matcher 之前,我們先快速瀏覽一下 CharSequence 這一新概念。

另外,爲方便起見 String 類爲運行正則表達式匹配提供了一些新程序作爲捷徑。這些將 5.3 節中討論。

1)CharSequence接口

正則表達式是根據字符序列進行模式匹配的。雖然 String 對象封裝了字符序列,但是它們並不能夠這樣做的唯一對象。

JDK 1.4 定義了一個名爲 CharSequence 的新接口,可描述特定不變的字符序列。該新接口是一個抽象(abstraction),它把字符序列從包含這些字符的具體實現(specific implementation)中 分離出來。 JDK 1.4 對“年高德勳”的 String 和 StringBuffer 類進行了改進, 用於實現 CharSequence 接口。 新的 CharBuffer 類(在第二章中介紹過)也實現了 CharSequence。 CharSequence 接口也在字符集映射中投入了使用(參見第六章)。

CharSequence 定義的 API 十分簡單。畢竟它沒有花太多“筆墨”描述字符序列。

package java.lang;

public interface CharSequence {

    int length();

    char charAt(int index);

    CharSequence subSequence(int start, int end);

    public String toString();
}

 

CharSequence 描述的每個字符序列通過 length( )方法會返回某個長度值。 通過調用 charAt( )可以得到序列的各個字符,其中索引是期望的字符位置(desired character position)。 字符位置從零到字符序列的長度之間,與我們熟悉的 String.charAt( )基本一樣。

toString( )方法返回的 String 對象包括所描述的字符序列。這可能很有用,如打印字符序 列。正如之前提過的,String 現在實現了 CharSequence。String 和 CharSequence 同爲不變的, 因此如果 CharSequence 描述一個完整的 String,那麼 CharSequence 的 toString( )方法返回的是 潛在的 String 對象而不是副本。如果備份對象是 StringBuffer 或 CharBuffer,系統將創建一個 新的 String 保存字符序列的副本。

最 後 通 過 調 用 subSequence( ) 方 法 會 創 建 一 個 新 的 CharSequence 描 述 子 範 圍 (subrange)。start 和 end 的指定方式與 String.substring( )的方式相同:start 必須是序列的有效 索引(valid index);end 必須比 start 大,標誌的是最末字符的索引加一。換句話說,start 是 起始索引(計算在內),end 是結束索引(不計算在內)。

CharSequence 接口因爲沒有賦值方法(mutator method)看上去似乎是不變的,但是基本 的實現對象可能不是不變的。 CharSequence 方法反映了基本對象的現狀。 如果狀態改變, CharSequence 方法返回的信息同樣會發生變化(見例 5-1)。如何你依賴 CharSequence 保持 穩定且不確認基礎的實現, 你可以調用 toString( )方法, 對字符序列拍個真實不變的快照。

例 5-1 CharSequence 接口實例

 

package org.example;

import java.nio.CharBuffer;

/**
 * Demonstrate behavior of java.lang.CharSequence as implemented
 * by String, StringBuffer and CharBuffer.
 *
 * @author Ron Hitchens ([email protected])
 */
public class CharSeq {

    public static void main(String[] argv) {
        StringBuffer stringBuffer = new StringBuffer("Hello World");
        CharBuffer charBuffer = CharBuffer.allocate(20);
        CharSequence charSequence = "Hello World";
        //直接來源於String
        printCharSequence(charSequence);
        //來源於StringBuffer
        charSequence = stringBuffer;
        printCharSequence(charSequence);
        //更改StringBuffer
        stringBuffer.setLength(0);
        stringBuffer.append("Goodbye cruel world");
        //相同、“不變的”CharSequence產生了不同的結果
        printCharSequence(charSequence);
        //從CharBuffer中導出CharSequence
        charSequence = charBuffer;
        charBuffer.put("xxxxxxxxxxxxxxxxxxxx");
        charBuffer.clear();
        charBuffer.put("Hello World");
        charBuffer.flip();
        printCharSequence(charSequence);
        charBuffer.mark();
        charBuffer.put("Seeya");
        charBuffer.reset();
        printCharSequence(charSequence);
        charBuffer.clear();
        printCharSequence(charSequence);
        //更改基礎CharBuffer會反映在只讀的CharSequence接口上
    }

    private static void printCharSequence(CharSequence cs) {
        System.out.println("length=" + cs.length()
                + ", content='" + cs.toString() + "'");
    }
}

以下是執行 CharSequence 的結果:

length=11, content='Hello World' 
length=11, content='Hello World' 
length=19, content='Goodbye cruel world' 
length=11, content='Hello World' 
length=11, content='Seeya World' 
length=20, content='Seeya Worldxxxxxxxxx'

2)Pattern類

Pattern 類封裝了正則表達式,它是你希望在目標字符序列中檢索的模式。匹配正則表 達式的代價可能非常高昂,因爲可能排列數量巨大,尤其是模式反覆應用的情況。大部分正則表達式處理器(包括 Perl 在內,在封裝中)首先會編譯表達式,然後利用編譯好的表達式在 輸入中進行模式檢測。

在這一點上 Java 正則表達式程序包別無兩樣。Pattern 類的實例是將一個編譯好的正 則表達式封裝起來。讓我們看看完整的 Pattern API,看看它是如何使用的。記住,這並不 是一個句法完整的類文件,它只中去掉了類主體的方法簽名。

package java.util.regex;

import java.util.regex.Matcher;

public final class Pattern implements java.io.Serializable {

    public static final int UNIX_LINES = 0x01;
    public static final int CASE_INSENSITIVE = 0x02;
    public static final int COMMENTS = 0x04;
    public static final int MULTILINE = 0x08;
    public static final int LITERAL = 0x10;
    public static final int DOTALL = 0x20;
    public static final int UNICODE_CASE = 0x40;
    public static final int CANON_EQ = 0x80;
    public static final int UNICODE_CHARACTER_CLASS = 0x100;

    public static boolean matches(String regex, CharSequence input)

    public static Pattern compile(String regex)

    public static Pattern compile(String regex, int flags)

    public String pattern()

    public int flags()

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

    public String[] split(CharSequence input)

    public Matcher matcher(CharSequence input)
}

上面所列的第一個方法 matches( )是個公用程序。它可以進行完整的匹配操作,並根據正 則表達式是否匹配整個的(entire)輸入序列返回一個布爾值。這種方法很容易上手,因爲 你無須追蹤任何對象;你要做的僅是調用一個簡單的靜態方法並測試結果。

    public boolean goodAnswer(String answer) {
        return (Pattern.matches("[Yy]es|[Yy]|[Tt]rue", answer));
    }

這種方法適用於默認設置尚可接受並且只需進行一次測試的情況。假如你要重複檢查同一 模式,假如你要找的模式是輸入的子序列,又假如你要設置非默認選項,那麼你應當創建一個 新的 Pattern 對象並使用新對象的 API 方法。

需要注意的是 Pattern 類並沒有公用建構函數。只有通過調用靜態工廠方法纔可以創建 新的實例。compile( )的兩個形式採用的都是正則表達式的 String 參數。返回的 Pattern 對 象包含被轉換成已編譯內部形式的正則表達式。 如果你提供的正則表達式形態異常, 那麼 compile( )工廠方法會拋出 java.util.regex.PatternSyntaxException(模式句法異 常)。這是未經檢查的異常,因此如果你對自己使用的正則表達式是否可行存在疑慮(例如它 傳遞給你是一個變量),那麼你可以把對 compile( )的調用放到 try/catch 塊中進行檢測。

compile( )的第二種形式接受標誌有一個位掩碼,這影響了正則表達式的默認編譯。這些標 志啓用了可選的編譯模式行爲,例如如何處理邊界或不區分大小寫等。(除 CANOB_EQ 外) 這些標誌(flag)同樣可由嵌入表達式內的子表達式啓用。標誌可以與布爾或(OR)表達式 結合使用,如下所示:

Pattern pattern = Pattern.compile("[A-Z][a-zA-Z]*", Pattern.CASE_INSENSITIVE | Pattern.UNIX_LINES);

所有標誌默認爲關閉狀態。表 5-1 總結了各個編譯時間選項的意義。

Pattern 類的實例是不變的,各個實例與對應的正則表達式綁定,無法修改。Pattern 對象也 是線程安全的,可被多個線程同時使用。

因此,一旦你拿到一個 Pattern,你能對它做什麼呢?

package java.util.regex;

import java.util.regex.Matcher;

public final class Pattern implements java.io.Serializable {

    // 這是部分的API列表
    public String pattern()

    public int flags()

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

    public String[] split(CharSequence input)

    public Matcher matcher(CharSequence input)
}

下面是兩個方法,作用是返回 Pattern 類 API 關於封裝表達式的信息。pattern( )返回的 String 被用於初建 Pattern 實例(建立對象時字符串被傳遞給 compile( ))。另一個是 flags( ), 返回的是在編譯模式時提供的標誌位掩碼。如果 Pattern 對象由無參數的 compile( )創建,則 flags( )返回的值是 0。返回的值反映的只是提供給 compile( )的明確標誌值;它不包括由正則表 達式模式中的嵌入表達式設置的任何等效標誌,等效標誌如表 5-1 第二列所示。

實例方法 split( )是公用程序,它使用模式作爲定界符(delimiter)來標記字符序列。這會 令人聯想到 StringTokenizer,但是它的功能更強,因爲定界符可以是匹配正則表達式的多字符 序列。另外,split( )方法是不監控狀態的,它返回的是字符串標誌的陣列而不是要求多個調用 程序並循環執行它們:

Pattern spacePat = Pattern.compile("\\s+");
String[] tokens = spacePat.split(input);

調用僅有一個參數的 split( )與調用兩個參數但第二參數爲零的該方法是等效的。split( )第 二個參數指示了輸入序列被正則表達式拆分的限制次數。限制參數的意義在於防止超負荷。非 正的值有特殊的意義。

如果傳遞給 split( )的限值爲負(任意負數),則字符序列將無限制地拆分直至輸入窮盡。 返回的陣列可能是任何長度。如果給定的限值爲零,則輸入將被無限拆分,但是結尾的空字符 串將不在結果陣列內。如果限值爲正,它設置的是返回的 String 陣列的最大值。假設限制爲 n,那麼正則表達式最大被用到 n-1 次。表 5-2 對這些組合進行了總結,生成該表格的代碼見例 5-2。

最後,matcher( )是爲已編譯的模式創建 Matcher 對象的工廠方法。匹配器(matcher)是 監控狀態的匹配引擎(stateful matching engine),它知道如何對目標字符序列的模式(Pattern 對象從何而來)進行匹配。在創建 Matcher 時你必須提供原始的輸入目標,但是隨後可提供不 同的輸入(5.2.3 節將對此進行討論)。

利用模式類拆分字符串

根據若干不同的正則表達式模式和不同的限值對相同的輸入字符串進行拆分,其結果如例 5-2 生成的矩陣所示。

例 5-2. 根據模式拆分字符串

package org.example;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Demonstrate behavior of splitting strings. The XML output created
 * here can be styled into HTML or some other useful form.
 * See poodle.xsl.
 *
 * @author Ron Hitchens ([email protected])
 */
public class Poodle {

    /**
     * Generate a matrix table of how Pattern.split( ) behaves with * various regex patterns and limit values.
     */
    public static void main(String[] argv) throws Exception {
        String input = "poodle zoo";
        Pattern space = Pattern.compile(" ");
        Pattern d = Pattern.compile("d");
        Pattern o = Pattern.compile("o");
        Pattern[] patterns = {space, d, o};
        int limits[] = {1, 2, 5, -2, 0};
        // 如果有參數就使用提供的參數。假設參數合理。
        // 用法:輸入模式[pattern ...]
        // 別忘了引用參數。
        if (argv.length != 0) {
            input = argv[0];
            patterns = collectPatterns(argv);
        }
        generateTable(input, patterns, limits);
    }

    /**
     * Output a simple XML document with the results of applying
     * the list of regex patterns to the input with each of the
     * limit values provided. I should probably use the JAX APIs
     * to do this, but I want to keep the code simple.
     */
    private static void generateTable(String input,
                                      Pattern[] patterns, int[] limits) {
        System.out.println("<?xml version='1.0'?>");
        System.out.println("<table>");
        System.out.println("\t<row>");
        System.out.println("\t\t<head>Input: " + input + "</head>");
        for (int i = 0; i < patterns.length; i++) {
            Pattern pattern = patterns[i];
            System.out.println("\t\t<head>Regex: <value>" + pattern.pattern() + "</value></head>");
        }
        System.out.println("\t</row>");
        for (int i = 0; i < limits.length; i++) {
            int limit = limits[i];
            System.out.println("\t<row>");
            System.out.println("\t\t<entry>Limit: "
                    + limit + "</entry>");
            for (int j = 0; j < patterns.length; j++) {
                Pattern pattern = patterns[j];
                String[] tokens = pattern.split(input, limit);
                System.out.print("\t\t<entry>");
                for (int k = 0; k < tokens.length; k++) {
                    System.out.print("<value>" + tokens[k] + "</value>");
                }
                System.out.println("</entry>");
            }
            System.out.println("\t</row>");
        }
        System.out.println("</table>");
    }

    /**
     * If command line args were given, compile all args after the
     * first as a Pattern. Return an array of Pattern objects.
     */
    private static Pattern[] collectPatterns(String[] argv) {
        List list = new LinkedList();
        for (int i = 1; i < argv.length; i++) {
            list.add(Pattern.compile(argv[i]));
        }
        Pattern[] patterns = new Pattern[list.size()];
        list.toArray(patterns);
        return (patterns);
    }
}

例 5-2 輸出的是描述結果矩陣的 XML 文檔,例 5-3 中的樣式表將 XML 轉換成 HTML 格 式便於顯示在網頁瀏覽器上。

例 5-3. 拆分矩陣樣式表

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <!--
        XSL stylesheet to transform the simple XML output of Poodle.java
        to HTML for display in a browser.Use an XSL processor such as xalan with this stylesheet to convert the XML to HTML.

        @author Ron Hitchens ([email protected])
    -->
    <xsl:output method="html"/>

    <xsl:template match="/">
        <html>
            <head>
                <title>Poodle Zoo</title>
            </head>
            <body>
                <xsl:apply-templates/>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="table">
        <table align="center" border="1" cellpadding="5">

            <xsl:apply-templates/>
        </table>
    </xsl:template>

    <xsl:template match="row">
        <tr>
            <xsl:apply-templates/>
        </tr>
    </xsl:template>

    <xsl:template match="entry">
        <td>
            <xsl:apply-templates/>
        </td>
    </xsl:template>

    <xsl:template match="head">
        <th>
            <xsl:apply-templates/>
        </th>
    </xsl:template>

    <xsl:template match="entry/value">
        <xsl:if test="position( ) != 1">
            <xsl:text>, </xsl:text>
        </xsl:if>
        <xsl:call-template name="simplequote"/>
    </xsl:template>

    <xsl:template name="simplequote" match="value">
        <code>
            <xsl:text>"</xsl:text>
            <xsl:apply-templates/>
            <xsl:text>"</xsl:text>
        </code>
    </xsl:template>
</xsl:stylesheet>

3)Matcher類

Matcher 類爲匹配字符序列的正則表達式模式提供了豐富的 API。Matcher 實例常常通 過對 Pattern 對象調用 matcher( )方法來創建的,它常常採用由該 Pattern 封裝的正則表達 式:

package java.util.regex;

import java.util.regex.Pattern;

public final class Matcher {

    public Pattern pattern()

    public Matcher reset()

    public Matcher reset(CharSequence input)

    public boolean matches()

    public boolean lookingAt()

    public boolean find()

    public boolean find(int start)

    public int start()

    public int start(int group)

    public int end()

    public int end(int group)

    public int groupCount()

    public String group()

    public String group(int group)

    public String replaceFirst(String replacement)

    public String replaceAll(String replacement)

    public StringBuffer appendTail(StringBuffer sb)

    public Matcher appendReplacement(StringBuffer sb, String replacement)
}

Matcher 類的實例是監控狀態型對象,它們封裝了與特定輸入字符序列匹配的具體正則 表達式。 Matcher 對象並不是線程安全的,因爲它們在方法調用之間有保有內狀態(hold internal state)。

一個 Matcher 實例來自一個 Pattern 實例,Matcher 對象的 pattern( )返回的是向後引 用(back reference),指向創建了 Matcher 的 Pattern 對象。

Matcher 對象可以重複使用,但是因其監控狀態屬性,爲了開始新匹配操作它們必須處 於已知狀態。這可通過調用 reset( )方法來實現,該方法在與匹配程序有關的 CharSequence 之 前爲模式匹配備好了對象。無參數的 reset( )將使用上次爲 Matcher 設置的 CharSequence。如 果你希望對新的字符序列進行匹配,那麼你可以將一個新的 CharSequence 傳遞給 reset( ),隨 後匹配將針對目標進行。例如,隨着你讀取各行的文件,你可以把它傳遞給 reset( )。參見例 5-4。

例 5-4. 簡單的文件 grep

package org.example;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Simple implementation of the ubiquitous grep command.
 * First argument is the regular expression to search for (remember to
 * quote and/or escape as appropriate). All following arguments are
 * filenames to read and search for the regular expression.
 *
 * @author Ron Hitchens ([email protected])
 */
public class SimpleGrep {

    public static void main(String[] argv) throws Exception {
        if (argv.length < 2) {
            System.out.println("Usage: regex file [ ... ]");
            return;
        }
        Pattern pattern = Pattern.compile(argv[0]);
        Matcher matcher = pattern.matcher("");
        for (int i = 1; i < argv.length; i++) {
            String file = argv[i];
            BufferedReader br = null;
            String line;
            try {
                br = new BufferedReader(new FileReader(file));
            } catch (IOException e) {
                System.err.println("Cannot read '" + file + "': " + e.getMessage());
                continue;
            }
            while ((line = br.readLine()) != null) {
                matcher.reset(line);
                if (matcher.find()) {
                    System.out.println(file + ": " + line);
                }
            }
            br.close();
        }
    }
}

例 5-5 演示的是 reset( )較複雜的應用,它允許 Matcher 作用於若干不同的字符序列。

例 5-5. 提取匹配的表達式

package org.example;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Validates email addresses.
 * Regular expression found in the Regular Expression Library
 * at regxlib.com. Quoting from the site,
 * "Email validator that adheres directly to the specification
 * for email address naming. It allows for everything from
 * ipaddress and country-code domains, to very rare characters
 * in the username."
 *
 * @author Michael Daudel ([email protected]) (original)
 * @author Ron Hitchens ([email protected]) (hacked)
 */
public class EmailAddressFinder {

    public static void main(String[] argv) {
        if (argv.length < 1) {
            System.out.println("usage: emailaddress ...");
        }
        // 編譯電子郵件地址檢測器的模式
        Pattern pattern = Pattern.compile(
                "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]" +
                        "{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))" +
                        "([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", Pattern.MULTILINE);
        // 爲模式製作一個Matcher對象
        Matcher matcher = pattern.matcher("");
        // 循環遍歷參數並在每個尋找地址
        for (int i = 0; i < argv.length; i++) {
            boolean matched = false;
            System.out.println("");
            System.out.println("Looking at " + argv[i] + " ...");
            // 將匹配器復位,查看當前實參字符串
            matcher.reset(argv[i]);
            // 遇到匹配時循環
            while (matcher.find()) {
                // 找到一個匹配
                System.out.println("\t" + matcher.group());
                matched = true;
            }
            if (!matched) {
                System.out.println("\tNo email addresses found");
            }
        }
    }
}

以下 EmailAddressFinder 運行一些標準地址上的輸出:

Looking at Ron Hitchens ,[email protected]., 
[email protected], 
[email protected],
Wilma <[email protected]> ...
[email protected] 
[email protected] 
[email protected] 
[email protected]

下一組方法返回的是正則表達式如何適用於目標字符串的布爾標誌。首先是 matches( ), 如果整個(entire)字符序列匹配正則表達式的模式,則它返回 true。反之如果模式匹配的只是 子序列,方法將返回 false。在文件中,這種方法用於選取恰好滿足一定模式的行是非常有用 的。這種行爲(behavior)與作用於 Pattern 類的公用程序 matches( )相同。

lookingAt( )方法與 matches( )相似,但是它不要求整個序列的模式匹配。如果正則表達式 模式匹配字符序列的 beginning(開頭),則 lookingAt( )返回 true。lookingAt( )方法往往從序列 的頭部開始掃描。該方法的名字暗示了匹配程序正在“查看”目標是否以模式開頭。如果返回 爲 true,那麼可以調用 start( )、end( )和 group( )方法匹配的子序列的範圍(隨後將給出更多關 於這些程序的內容)。

find( )方法運行的是與 lookingAt( )相同類型的匹配操作,但是它會記住前一個匹配的位置 並在之後重新開始掃描。從而允許了相繼調用 find( )對輸入進行逐句比對,尋找嵌入的匹配。 復位後第一次調用該方法,則掃描將從輸入序列的首個字符開始。在隨後調用中,它將從前一 個匹配的子序列後面的第一個字符重新開始掃描。如各個調用來說, 如果找到了模式將返回 true;反之將返回 false。通常你會使用 find( )循環訪問一些文本來查找其中所有匹配的模式。

帶位置參數的 find( )會在給定的索引位置進行隱式復位並從該位置開始掃描。然後如果需 要可以調用無參數的 find( )掃描輸入序列剩餘的部分。

一旦檢查到匹配,你可以通過調用 start( )和 end( )確定匹配位於字符序列的什麼位置。 Start( )方法返回的是匹配序列首個字符的索引;end( )方法返回的值等於匹配序列最末字符的 索引加一。這些返回值與 CharSequence.subsequence( )的返回值一致,可直接用於提取匹配的 子序列。

        CharSequence subseq;
        if (matcher.find()) {
            subseq = input.subSequence(matcher.start(), matcher.end());
        }

一些正則表達式可以匹配空字符串,這種情況下 start( )和 end( )將返回相同的值。只有當 匹配之前已經過 matches( )、lookingAt( )或檢測 find( )的檢測,start( )和 end( )返回的值纔有意 義。如果沒有檢測到匹配或最後的匹配嘗試返回的是 false,那麼調用 start( )或 end( )將導致java.lang.IllegalStateException(Java 語言非法狀態異常)。

爲了瞭解帶有 group 參數的 start( )和 end( ),我們首先需要知道表達式捕獲組(expression capture group)。(見圖 5-2)

正則表達式可能包含稱爲捕獲組(capture group)的子表達式,它們被小括號括了起來。 在正則表達式的求值期間將保存匹配這些捕獲組表達式的輸入子序列。一旦完全匹配操作完 成,這些保存的代碼片斷可通過確定相應的組號從 Matcher 對象上重新獲取。

捕獲組可以嵌套使用,數量可以通過從左到右計算左括弧(開括號)得到。無論整個表達 式是否有子組,它的捕獲組總能記爲組零(group zero)。例如,正則表達式 A((B)(C(D)))可能 有的捕獲組編號如表 5-3 所示。

這種分組句法存在異常事件。以(? 開頭的組是個純的(pure)或說是無法捕獲的組。它的 值無法保存且它對無法計算捕獲組編號。(句法細節參見表 5-7。)

讓我們看看方法作用於捕獲組的更多細節:

package java.util.regex;

public final class Matcher {

    // 這是API列表的一部分
    public int start()

    public int start(int group)

    public int end()

    public int end(int group)

    public int groupCount()

    public String group()

    public String group(int group)
}

捕獲組在正則表達式模式中的編號由 groupCount( )方法返回。該值來自原始的 Pattern 對 象,是不可變的。組號必須爲正且小於 groupCount( )返回的值。傳遞超出範圍的組號將導致 java.lang.IndexOutOfBoundsException(java 語言索引出界異常)。

可以將捕獲組號傳遞給 start( )和 end( )來確定子序列是否匹配已知的捕獲組子表達式。有 可能出現這樣一種情況,即整個表達式成功匹配但是有一個或多個的捕獲組無法匹配。如果請 求的捕獲組當前沒有設置則 start( )和 end( )方法的返回值將爲-1。

(正如之前看到的)你可以利用 start( )和 end( )返回的值從輸入的 CharSequence 中提取出 匹配的子序列,但是 group( )方法爲此提供了更簡單的方式。調用帶數字參數的 group( )將返回 一個字段,該字段是匹配特殊捕獲組的子序列。如果你調用的 group( )不含參數,則返回將是 與整個正則表達式(組零)匹配的子序列。代碼如下:

        String match0 = input.subSequence(matcher.start(), matcher.end()).toString();
        String match2 = input.subSequence(matcher.start(2), matcher.end(2)).toString();

上述代碼與下列代碼等效:

        String match0 = matcher.group();
        String match2 = matcher.group(2);

最後讓我們看看 Matcher 對象解決修改字符序列的方法。正則表達式最常見的應用之一是 查找並替換(search-and-replace)。這種應用使用 replaceFirst( )和 replaceAll( )可以輕輕鬆鬆就 搞定。它們的行爲方式是相同的,區別在於 replaceFirst( )在找到第一個匹配後就會停止,而 replaceAll( )將循環執行直到替換完所有的匹配。二者都帶有 String 參數,String 參數是用於替 換輸入字符序列中匹配模式的替換值(replacement value)。

package org.example;

public final class Matcher {

    // 這是API列表的一部分
    public String replaceFirst(String replacement)

    public String replaceAll(String replacement)
}

上文提過,捕獲組在正則表達式內可以向後引用(back-reference)。它們也可以被你提供 組 replaceFirst( )或 replaceAll( )的替換字符串引用。捕獲組號通過添加美元符號可嵌入替換字符串中。當替換字符串被替換成結果字符串時,每次出現的​g 將被 group( )返回的值代替。如果你想在替換字符串使用字面量(literal)美元符號, 那麼你必須在它前面加個反斜槓符號 ($)。如果想要傳遞反斜槓符號,你必須多加一個反斜槓(\)。如果你想在捕獲組引用後 面跟上字面量的數值型數字,那麼你可以用反斜槓將它們與組號分開,像這樣:123$2\456。 表 5-4 對此給出了一些例子。示例代碼見例 5-6。

例 5-6. 正則表達式替換

package java.util.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Exercise the replacement capabilities of the java.util.regex.Matcher
 * class. Run this code from the command line with three or more arguments.
 * 1) First argument is a regular expression
 * 2) Second argument is a replacement string, optionally with capture
 * group references ($1, $2, etc)
 * 3) Any remaining arguments are treated as input strings to which the
 * regular expression and replacement strings will be applied.
 * The effect of calling replaceFirst( ) and replaceAll( ) for each input
 * string will be listed.
 * <p>
 * Be careful to quote the commandline arguments if they contain spaces
 * or special characters.
 * <p>
 * * @author Ron Hitchens ([email protected])
 */
public class RegexReplace {

    public static void main(String[] argv) {
        // 完整性檢查,至少需要三個參數
        if (argv.length < 3) {
            System.out.println("usage: regex replacement input ...");
            return;
        }
        // 用助詞符號名保存正則及替換字符串
        String regex = argv[0];
        String replace = argv[1];
        // 編譯表達式;一次只能編譯一個
        Pattern pattern = Pattern.compile(regex);
        // 得到Matcher實例,暫時先使用虛設的輸入字符串
        Matcher matcher = pattern.matcher("");
        // 打印輸出用於參考
        System.out.println(" regex: '" + regex + "'");
        System.out.println(" replacement: '" + replace + "'");
        // 對各個剩餘的參數字符串應用正則/替換
        for (int i = 2; i < argv.length; i++) {
            System.out.println("------------------------");
            matcher.reset(argv[i]);
            System.out.println(" input: '" + argv[i] + "'");
            System.out.println("replaceFirst( ): '" + matcher.replaceFirst(replace) + "'");
            System.out.println(" replaceAll( ): '" + matcher.replaceAll(replace) + "'");
        }
    }
}

下列是運行 RegexReplace 後的輸出結果:

regex: '([bB])yte' 
replacement: '$1ite' 
-----------------------
input: 'Bytes is bytes' 
replaceFirst( ): 'Bites is bytes' 
replaceAll( ): 'Bites is bites'

記住:正則表達式會在你提供的字符串中翻譯反斜槓。另外在字面量的 String 中,Java 編譯器要求各個反斜槓需要有兩個反斜槓, 即如果你想在正則中轉義(escape)一個反斜 槓),那麼你在編譯過的 String 需要中需要兩反斜槓。如果在編譯的正則字符串中需要兩個 連續的反斜槓,那麼在 Java 源代碼中需要四個連接的反斜槓。

爲了生成 a\b 的替換序列,replaceAll( )的 String 字面量參數必須是 a\\b(見例 57)。統計這些反斜槓時千萬要小心啊!

例 5-7. 正則表達式中的反斜槓

package org.example;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Demonstrate behavior of backslashes in regex patterns.
 *
 * @author Ron Hitchens ([email protected])
 */
public class BackSlashes {

    public static void main(String[] argv) {
        // 在輸入中把“a\b”替換成XYZ或ABC
        String rep = "a\\\\b";
        String input = "> XYZ <=> ABC <";
        Pattern pattern = Pattern.compile("ABC|XYZ");
        Matcher matcher = pattern.matcher(input);
        System.out.println(matcher.replaceFirst(rep));
        System.out.println(matcher.replaceAll(rep));
        // 在輸入中更改所有的新行來轉義, DOS-like CR/LF 
        rep = "\\\\r\\\\n";
        input = "line 1\nline 2\nline 3\n";
        pattern = Pattern.compile("\\n");
        matcher = pattern.matcher(input);
        System.out.println("");
        System.out.println("Before:");
        System.out.println(input);
        System.out.println("After (dos-ified, escaped):");
        System.out.println(matcher.replaceAll(rep));
    }
}

下列是運行BackSlashes的輸出結果:

> a\b <=> ABC <
> a\b <=> a\b <

Before: 
line 1 
line 2 
line 3

After (dos-ified, escaped): 
line 1\r\nline 2\r\nline 3\r\n

Matcher API 列出了兩個追加方法,它們在循環訪問輸入字符序列時很有用,它們重複 調用 find( )。

package java.util.regex;

public final class Matcher { // 這是部分的API列表

    public StringBuffer appendTail(StringBuffer sb)

    public Matcher appendReplacement(StringBuffer sb, String replacement)
}

追加方法不是返回已經運行過替換的新 String 而是添加到你提供的 StringBuffer 對 象上。這就允許你決定在找到匹配的各點是否進行替換或計算與多個輸入字符串的匹配結果。 通過 appendReplacement( )和 appendTail( ),你可以全面控制查找和替換過程。

Matcher 對象記住的狀態信息位(the bits of state information)之一是追加位置(append position)。追加位置是用於記住輸入字符序列的量,這些字符序列已經通 過之前調用 appendReplacement( )複製了出來。當調用 appendReplacement( )時,將發生如下過程:

  1. 從 輸 入 中 讀 取 字 符 是 從 當 前 追 加 位 置 開 始 , 讀 取 的 字 符 將 被 添 加 到 已 知 的 StringBuffer 中。最後複製的字符就在匹配模式的首個字符之前。這個字符位於 start( )返回的索引減一的位置。

  2. 如先前描述的, 替換字符串被添加給 StringBuffer 並替換任何嵌入的捕獲組引 用。

  3. 追加位置更新成跟在匹配模式後面的字符的索引,這個索引是 end( )返回的值。

 

僅當前一個匹配操作成功(通常調用 find( ))appendReplacement( )方法才能正常工作。如 果前一個匹配返回的是 false 或在復位後立即調用該方法,你將得到一個“令人愉快的獎 勵”:java.lang.IllegalStateException(java 語言非法狀態異常)。

但是別忘了:在輸入中除了最後的模式匹配外還有剩餘的字符。你很可能不想失去它們, 但是 appendReplacement( )不會複製它們,並且在 find( )無法找到更多的匹配後 end( )將不會返 回有用的值。這種情況下 appendTail( )方法正好可以複製輸入中餘下的部分。它僅是複製了從 當前追加位置到輸入結果的所有字符,並把它們追加到給定的 StringBuffer 中。下列代碼 是 appendReplacement( )和 appendTail( )典型的使用情況。

        Pattern pattern = Pattern.compile("([Tt])hanks");
        Matcher matcher = pattern.matcher("Thanks, thanks very much");
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            if (matcher.group(1).equals("T")) {
                matcher.appendReplacement(sb, "Thank you");
            } else {
                matcher.appendReplacement(sb, "thank you");
            }
        }
        matcher.appendTail(sb);

經過追加操作的該序列生成的 StringBuffer 對象 sb 包含字符串“Thank you, thank you very much”。例 5-8 是個完整的代碼示例,它顯示了這種替換類型及運行相同替換的代替方式。在這個簡單例子裏,可以使用捕獲組的值,因爲匹配模式的首個字母與替換的首個字母相同。在 更復雜的例子中, 輸入與替換值間可能不存在重疊部分(overlap)。 利用 Matcher.find( )和 Matcher.appendReplacement( ),你可以通過編程方式調解每個替換,從而可能隨時在各個點引 入不同的替換值。

例 5-8. 正則表達式追加/替換

package org.example;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Test the appendReplacement() and appendTail() methods of the
 * java.util.regex.Matcher class.
 *
 * @author Ron Hitchens ([email protected])
 */
public class RegexAppend {

    public static void main(String[] argv) {
        String input = "Thanks, thanks very much";
        String regex = "([Tt])hanks";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        StringBuffer sb = new StringBuffer();
        //循環直到遇到匹配
        while (matcher.find()) {
            if (matcher.group(1).equals("T")) {
                matcher.appendReplacement(sb, "Thank you");
            } else {
                matcher.appendReplacement(sb, "thank you");
            }
        }
        // 完成到StringBuffer的傳送
        matcher.appendTail(sb);
        // 打印結果
        System.out.println(sb.toString());
        // 讓我們再試試在替換中使用$n轉義
        sb.setLength(0);
        matcher.reset();
        String replacement = "$1hank you";
        //循環直到遇到匹配
        while (matcher.find()) {
            matcher.appendReplacement(sb, replacement);
        }
        // 完成到StringBuffer傳送
        matcher.appendTail(sb);
        // 打印結果
        System.out.println(sb.toString());
        // 再來一次,簡單的方法(因爲這個父子很簡單)
        System.out.println(matcher.replaceAll(replacement));
        // 最後一次,只使用字符串 
        System.out.println(input.replaceAll(regex, replacement));
    }
}

3.String類的正則表達式方法

從前幾個章節來看,字符串和正則表達式關聯密切是顯而易見的。 這是很自然,我們的 “老朋友”Sting 類已經添加了一些公用程序來執行常見的正則表達式操作:

package java.lang;

public final class String implements java.io.Serializable, Comparable, CharSequence {

    // 這是部分的API列表
    public boolean matches(String regex)

    public String[] split(String regex)

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

    public String replaceFirst(String regex, String replacement)

    public String replaceAll(String regex, String replacement)
}

所有新的 String 方法是 Pattern 或 Matcher 類的傳遞調用(pass-through call)。現在你知道 了 Pattern 和 Matcher 是如何使用的,並且利用這些 String 公用程序的交互操作(interoperate) 應當是傻瓜式的。表 5-6 對這些方法作了總結,不再對方法一一贅述。

表 5-6 中,假定 String 對象名爲 input,Pattern 對象名爲 pat,而 Matcher 對象名爲 match:

        String input = "Mary had a little lamb";
        String[] tokens = input.split("\\s+");// 在空白符上拆分

 

在 JDK 1.4 之前,沒有一個正則表達式公用程序緩存任何表達式或進行其它優化。一些 JVM 實現可能選擇緩存或重用模式對象,但是它們卻不足以採信。如果你希望重複應用相同 的模式匹配操作,那麼使用 java.util.regex 中的類更爲高效。

4.Java正則表達式句法

下面是 java.util.regex 包支持的正則表達式句法的總結。Java 世界“瞬息萬變”,因此你 需要時常檢查一下你正在使用的 Java 實現提供的當前文檔。這裏提供的信息爲你提供了邁出 第一步的快速參考。

java.util.regex 類可全面感知 Unicode(fully Unicode-aware),它們完全遵循《Unicode 技 術報告#18:Unicode 正則表達式指南》中的準則。文章參見:

http://www.unicode.org/unicode/reports/tr18

之前提過,java.util.regex 的句法與 Perl 相似,卻又不盡相同。java.util.regex 缺少的是能夠 在表達式中嵌入 Perl 代碼這一主要功能(這要求插入完整的 Perl 解譯器)。Java 句法中添加 的 重 要 功 能 是 佔 有 型 量 詞 ( possessive quantifier ) , 它 們 比 常 規 的 貪 婪 量 詞 ( greedy quantifier)還要“貪婪”。佔有型量詞會盡可能地多匹配目標,即便這意味着表達式剩餘的部 分 無 法 匹 配 。 Java 正 則 表 達 式 也 支 持 一 些 Perl 不 支 持 的 Unicode 轉 義 序 列 。 java.util.regex.Pattern 完整詳情請參閱 Javadoc 頁面。

表 5-7 是正則表達式一覽表。它複製的是《Java In A Nutshell》第四版(O’Reilly)。

5.面向對象的文件Grep

例 5-9 實現了一個面向熟悉的 grep 命令的對象。Grep 類的實例包含正則表達式,可用於 瀏覽相同模式的不同文件。Grep.grep( )程序的結果是類型安全的 Grep.MatchedLine 對象陣列。 MatchedLine 類包含在 Grep 中的類。你必須將它作爲 Grep.MatchedLine 引用或將它單獨導入。

package org.example;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A file searching class, similar to grep, which returns information
 * about lines matched in the specified files. Instances of this class
 * are tied to a specific regular expression pattern and may be applied
 * repeatedly to multiple files. Instances of Grep are thread safe, they
 * may be shared.
 *
 * @author Michael Daudel ([email protected]) (original)
 * @author Ron Hitchens ([email protected]) (hacked)
 */
public class Grep {

    // 用於該實例的模式
    private Pattern pattern;

    /**
     * Instantiate a Grep object for the given pre-compiled Pattern object.
     *
     * @param pattern A java.util.regex.Pattern object specifying the
     *                pattern to search for.
     */
    public Grep(Pattern pattern) {
        this.pattern = pattern;
    }

    /**
     * Instantiate a Grep object and compile the given regular expression
     * string.
     *
     * @param regex      The regular expression string to compile into a
     *                   Pattern for internal use.
     * @param ignoreCase If true, pass Pattern.CASE_INSENSITIVE to the
     *                   Pattern constuctor so that seaches will be done without regard
     *                   to alphabetic case. Note, this only applies to the ASCII
     *                   character set. Use embedded expressions to set other options.
     */
    public Grep(String regex, boolean ignoreCase) {
        this.pattern = Pattern.compile(regex, (ignoreCase) ? Pattern.CASE_INSENSITIVE : 0);
    }

    /**
     * Instantiate a Grep object with the given regular expression string,
     * with default options.
     */
    public Grep(String regex) {
        this(regex, false);
    }
    // ---------------------------------------------------------------

    /**
     * Perform a grep on the given file.
     *
     * @param file A File object denoting the file to scan for the
     *             regex given when this Grep instance was constructed.
     * @return A type-safe array of Grep.MatchedLine objects describing
     * the lines of the file matched by the pattern.
     * @throws IOException If there is a problem reading the file.
     */
    public MatchedLine[] grep(File file)
            throws IOException {
        List list = grepList(file);
        MatchedLine matches[] = new MatchedLine[list.size()];
        list.toArray(matches);
        return (matches);
    }

    /**
     * Perform a grep on the given file.
     *
     * @param fileName A String filename denoting the file to scan for the
     *                 regex given when this Grep instance was constructed.
     * @return A type-safe array of Grep.MatchedLine objects describing
     * the lines of the file matched by the pattern.
     * @throws IOException If there is a problem reading the file.
     */
    public MatchedLine[] grep(String fileName)
            throws IOException {
        return (grep(new File(fileName)));
    }

    /**
     * Perform a grep on the given list of files. If a given file cannot
     * be read, it will be ignored as if empty.
     *
     * @param files An array of File objects to scan.
     * @return A type-safe array of Grep.MatchedLine objects describing
     * the lines of the file matched by the pattern.
     */
    public MatchedLine[] grep(File[] files) {
        List aggregate = new LinkedList();
        for (int i = 0; i < files.length; i++) {
            try {
                List temp = grepList(files[i]);
                aggregate.addAll(temp);
            } catch (IOException e) {
                // 忽略I/O異常
            }
        }
        MatchedLine matches[] = new MatchedLine[aggregate.size()];
        aggregate.toArray(matches);
        return (matches);
    }
// -------------------------------------------------------------

    /**
     * Encapsulation of a matched line from a file. This immutable
     * object has five read-only properties:
     * <ul>
     *     <li>getFile( ): The File this match pertains to.</li>
     *     <li>getLineNumber( ): The line number (1-relative) within the file where the match was found.</li>
     *     <li>getLineText( ): The text of the matching line</li>
     *     <li>start( ): The index within the line where the matching pattern begins.</li>
     *     <li>end( ): The index, plus one, of the end of the matched character sequence.</li>
     * </ul>
     */
    public static class MatchedLine {

        private File file;
        private int lineNumber;
        private String lineText;
        private int start;
        private int end;

        MatchedLine(File file, int lineNumber, String lineText, int start, int end) {
            this.file = file;
            this.lineNumber = lineNumber;
            this.lineText = lineText;
            this.start = start;
            this.end = end;
        }

        public File getFile() {
            return (this.file);
        }

        public int getLineNumber() {
            return (this.lineNumber);
        }

        public String getLineText() {
            return (this.lineText);
        }

        public int start() {
            return (this.start);
        }

        public int end() {
            return (this.end);
        }
    }
// -----------------------------------------------------------

    /**
     * Run the grepper on the given File.
     *
     * @return A (non-type-safe) List of MatchedLine objects.
     */
    private List grepList(File file) throws IOException {
        if (!file.exists()) {
            throw new IOException("Does not exist: " + file);
        }
        if (!file.isFile()) {
            throw new IOException("Not a regular file: " + file);
        }
        if (!file.canRead()) {
            throw new IOException("Unreadable file: " + file);
        }
        LinkedList list = new LinkedList();
        FileReader fr = new FileReader(file);
        LineNumberReader lnr = new LineNumberReader(fr);
        Matcher matcher = this.pattern.matcher("");
        String line;
        while ((line = lnr.readLine()) != null) {
            matcher.reset(line);
            if (matcher.find()) {
                list.add(new MatchedLine(file, lnr.getLineNumber(), line, matcher.start(), matcher.end()));
            }
        }
        lnr.close();
        return (list);
    }
    // ---------------------------------------------------------------

    /**
     * Test code to run grep operations. Accepts two command-line
     * options: -i or --ignore-case, compile the given pattern so
     * that case of alpha characters is ignored. Or -1, which runs
     * the grep operation on each individual file, rather that passing
     * them all to one invocation. This is just to test the different
     * methods. The printed ouptut is slightly different when -1 is
     * specified.
     */
    public static void main(String[] argv) {
        // 設置默認值
        boolean ignoreCase = false;
        boolean onebyone = false;
        List argList = new LinkedList();
        // 採集變量
        // 循環遍歷變量,查找轉換並保存模式及文件名
        for (int i = 0; i < argv.length; i++) {
            if (argv[i].startsWith("-")) {
                if (argv[i].equals("-i") || argv[i].equals("--ignore-case")) {
                    ignoreCase = true;
                }
                if (argv[i].equals("-1")) {
                    onebyone = true;
                }
                continue;
            }
            // 不是轉移(switch),將其添加到列表中
            argList.add(argv[i]);
        }
        // 是否有足夠的變量可以運行?
        if (argList.size() < 2) {
            System.err.println("usage: [options] pattern filename ...");
            return;
        }
        // 列表中第一個變量將被作爲正則模式。
        // 將模式及忽略大小寫標誌的當前值傳遞給新的Grep對象。
        Grep grepper = new Grep((String) argList.remove(0), ignoreCase);
        // 隨意點,拆分成調用grep程序和打印結果兩種方式
        if (onebyone) {
            Iterator it = argList.iterator();
            // 循環遍歷文件名並用grep處理它們
            while (it.hasNext()) {
                String fileName = (String) it.next();
                // 在每次grep前先打印文件名
                System.out.println(fileName + ":");
                MatchedLine[] matches = null;
                // 捕獲異常
                try {
                    matches = grepper.grep(fileName);
                } catch (
                        IOException e) {
                    System.err.println("\t*** " + e);
                    continue;
                }
                // 打印匹配行的資料
                for (int i = 0; i < matches.length; i++) {
                    MatchedLine match = matches[i];
                    System.out.println(" " + match.getLineNumber() + " [" + match.start() + "-" + (match.end() - 1) + "]: " + match.getLineText());
                }
            }
        } else {
            // 把文件名列表轉換到File陣列中
            File[] files = new File[argList.size()];
            for (int i = 0; i < files.length; i++) {
                files[i] = new File((String) argList.get(i));
            }
            // 運行grep程序;忽略無法讀取的文件
            MatchedLine[] matches = grepper.grep(files);
            // 打印匹配行的資料
            for (int i = 0; i < matches.length; i++) {
                MatchedLine match = matches[i];
                System.out.println(match.getFile().getName() + ", " + match.getLineNumber() + ": " + match.getLineText());
            }
        }
    }
}

 

總結

本章中我們討論了期待已久的在 1.4 版本中添加到 J2SE 平臺上的正則表達式 類:

CharSequence

在 5.2.1 節中我們瞭解了新的 CharSequence 接口,知道了它用若干個類實現了 抽象地描述字符序列。

Pattern

Pattern 類把正則表達式封裝在不變的對象實例中。在 5.2.2 節我們看到了 Pattern 的 API 並學會了如何通過編譯表達式字符串來創建實例。我們還知道了用於進行 單次匹配的一些靜態實用程序。

Matcher

Matcher 類是狀態機對象,它在輸入字符序列上使用了 Pattern 對象來尋找輸入中 匹配的模式。5.2.3 節描述了 Matcher API,包括如何在 Pattern 對象上創建新的 Matcher 實例及如何運行各種類型的匹配操作。

String

1.4 版本中添加的 String 類有了一些新的正則表達式公用程序。5.3 節對此作了總 結。

 

java.util.regex.Pattern 支持的正則表達式句法已在表 5-7 中列出。其句法與 Perl 5 很接近。

現在我們將這次遊歷劃上圓滿的句號。 在下一章中, 你將領略字符集這一充滿 “異國情調”並且有時神祕的世界。

摘自JAVA NIO(中文版)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章