Java的Character類別提供了isWhitespace方法,可以用來判斷指定的字元是否是空白字元,然而這邊判斷的空白字元並不單純只是我們從鍵盤按下空白鍵所出現的 (字元值為32),而是還包括TAB字元、換行字元等等會讓文字在顯示時被空白區域隔開的字元。正規表示式所使用的javaWhitespace樣本(pattern)的空白字元判斷標準與Character.isWhitespace()相同,但正規表示式也同時能用\s或是\p{Space}來判斷空白字元,它們到底有什麼不同呢?



Character.isWhitespace()

根據Java的API文件,Character.isWhitespace()會把下列字元當作是空白字元:

  • 包含在SPACE_SEPARATORLINE_SEPARATORPARAGRAPH_SEPARATOR字元集內的字元,除了\u00A0\u2007\u202F之外。
  • 水平定位符號(TAB字元)\u0009(\t)。
  • 換行字元\u000A(\n)。
  • 垂直定位符號\u000B
  • 換頁字元\u000C
  • 回車字元\u000D(\r)。
  • 檔案分割字元\u001C
  • 群組分隔字元\u001D
  • 記錄分隔字元\u001E
  • 單元分隔字元\u001F

SPACE_SEPARATORLINE_SEPARATORPARAGRAPH_SEPARATOR字元集有哪些字元?

利用以下Java程式,可以找出SPACE_SEPARATORLINE_SEPARATORPARAGRAPH_SEPARATOR字元集中所有的字元。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringBuilder;

public class WhitespaceCharset {

    public static void appendChar(final StringBuilder sb, char c) {
        sb.append(String.format("\\u%04x ", (int) c));

        final byte[] bytes = new String(new char[]{c}).getBytes();

        sb.append(String.format("%-17s ",Arrays.toString(bytes))).append(' ');

        final int[] ints = new int[bytes.length];

        for (int i = 0; i < bytes.length; ++i) {
            int n = (int) bytes[i];

            if (n < 0) {
                n += 256;
            }

            ints[i] = n;
        }

        sb.append(Arrays.toString(ints)).append('\n');
    }

    public static void main(final String[] args) {
        final ArrayList<Character> alSpaceSeparators = new ArrayList<>();
        final ArrayList<Character> alLineSeparators = new ArrayList<>();
        final ArrayList<Character> alParagraphSeparators = new ArrayList<>();

        for (char ch = Character.MIN_VALUE; ch < Character.MAX_VALUE; ++ch) {
            int chType = Character.getType(ch);
            
            if (Character.SPACE_SEPARATOR == chType) {
                alSpaceSeparators.add(ch);
            } else if (Character.LINE_SEPARATOR == chType) {
                alLineSeparators.add(ch);
            } else if (Character.PARAGRAPH_SEPARATOR == chType) {
                alParagraphSeparators.add(ch);
            }
        }

        final StringBuilder sb = new StringBuilder();

        sb.append("SPACE_SEPARATOR").append('\n');
        alSpaceSeparators.forEach((c) -> appendChar(sb, c));
        
        sb.append('\n').append("LINE_SEPARATOR").append('\n');
        alLineSeparators.forEach((c) -> appendChar(sb, c));
        
        sb.append('\n').append("PARAGRAPH_SEPARATOR").append('\n');
        alParagraphSeparators.forEach((c) -> appendChar(sb, c));

        System.out.println(sb.toString().trim());
    }
}

輸出的第一個欄是字元的16進制字元值,第二個欄位是以UTF-8編碼之後的byte陣列,第三個欄位是以UTF-8編碼之後的無號int陣列。

執行結果如下(會根據Java版本不同而有所差異):

SPACE_SEPARATOR
\u0020 [32]               [32]
\u00a0 [-62, -96]         [194, 160]
\u1680 [-31, -102, -128]  [225, 154, 128]
\u180e [-31, -96, -114]   [225, 160, 142]
\u2000 [-30, -128, -128]  [226, 128, 128]
\u2001 [-30, -128, -127]  [226, 128, 129]
\u2002 [-30, -128, -126]  [226, 128, 130]
\u2003 [-30, -128, -125]  [226, 128, 131]
\u2004 [-30, -128, -124]  [226, 128, 132]
\u2005 [-30, -128, -123]  [226, 128, 133]
\u2006 [-30, -128, -122]  [226, 128, 134]
\u2007 [-30, -128, -121]  [226, 128, 135]
\u2008 [-30, -128, -120]  [226, 128, 136]
\u2009 [-30, -128, -119]  [226, 128, 137]
\u200a [-30, -128, -118]  [226, 128, 138]
\u202f [-30, -128, -81]   [226, 128, 175]
\u205f [-30, -127, -97]   [226, 129, 159]
\u3000 [-29, -128, -128]  [227, 128, 128]

LINE_SEPARATOR
\u2028 [-30, -128, -88]   [226, 128, 168]

PARAGRAPH_SEPARATOR
\u2029 [-30, -128, -87]   [226, 128, 169]

所以全部有哪些字元會被判斷為空白字元呢?

利用以下Java程式,可以找出所有會被判斷為空白字元的字元:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringBuilder;

public class Whitespace {

    public static void appendChar(final StringBuilder sb, char c) {
        sb.append(String.format("\\u%04x ", (int) c));

        final byte[] bytes = new String(new char[]{c}).getBytes();

        sb.append(String.format("%-17s ", Arrays.toString(bytes))).append(' ');

        final int[] ints = new int[bytes.length];

        for (int i = 0; i < bytes.length; ++i) {
            int n = (int) bytes[i];

            if (n < 0) {
                n += 256;
            }

            ints[i] = n;
        }

        sb.append(Arrays.toString(ints)).append('\n');
    }

    public static void main(String[] args) {
        final ArrayList<Character> alWhiteSpaces = new ArrayList<>();

        for (char ch = Character.MIN_VALUE; ch < Character.MAX_VALUE; ++ch) {
            if (Character.isWhitespace(ch)) {
                alWhiteSpaces.add(ch);
            }
        }

        final StringBuilder sb = new StringBuilder();

        alWhiteSpaces.forEach((c) -> appendChar(sb, c));

        System.out.println(sb.toString().trim());
    }
}

輸出的第一個欄是字元的16進制字元值,第二個欄位是以UTF-8編碼之後的byte陣列,第三個欄位是以UTF-8編碼之後的無號int陣列。

執行結果如下(會根據Java版本不同而有所差異):

\u0009 [9]                [9]
\u000a [10]               [10]
\u000b [11]               [11]
\u000c [12]               [12]
\u000d [13]               [13]
\u001c [28]               [28]
\u001d [29]               [29]
\u001e [30]               [30]
\u001f [31]               [31]
\u0020 [32]               [32]
\u1680 [-31, -102, -128]  [225, 154, 128]
\u180e [-31, -96, -114]   [225, 160, 142]
\u2000 [-30, -128, -128]  [226, 128, 128]
\u2001 [-30, -128, -127]  [226, 128, 129]
\u2002 [-30, -128, -126]  [226, 128, 130]
\u2003 [-30, -128, -125]  [226, 128, 131]
\u2004 [-30, -128, -124]  [226, 128, 132]
\u2005 [-30, -128, -123]  [226, 128, 133]
\u2006 [-30, -128, -122]  [226, 128, 134]
\u2008 [-30, -128, -120]  [226, 128, 136]
\u2009 [-30, -128, -119]  [226, 128, 137]
\u200a [-30, -128, -118]  [226, 128, 138]
\u2028 [-30, -128, -88]   [226, 128, 168]
\u2029 [-30, -128, -87]   [226, 128, 169]
\u205f [-30, -127, -97]   [226, 129, 159]
\u3000 [-29, -128, -128]  [227, 128, 128]

正規表示式匹配的空白字元

執行以下Java程式,即可分別找出所有會被\s\p{Space}\p{javaWhitespace}匹配的字元:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringBuilder;

public class WhitespaceRegex {

    public static void appendChar(final StringBuilder sb, char c) {
        sb.append(String.format("\\u%04x ", (int) c));

        final byte[] bytes = new String(new char[]{c}).getBytes();

        sb.append(String.format("%-17s ", Arrays.toString(bytes))).append(' ');

        final int[] ints = new int[bytes.length];

        for (int i = 0; i < bytes.length; ++i) {
            int n = (int) bytes[i];

            if (n < 0) {
                n += 256;
            }

            ints[i] = n;
        }

        sb.append(Arrays.toString(ints)).append('\n');
    }

    public static void main(String[] args) {
        final ArrayList<Character> alP1 = new ArrayList<>();
        final ArrayList<Character> alP2 = new ArrayList<>();
        final ArrayList<Character> alP3 = new ArrayList<>();

        final Pattern p1 = Pattern.compile("\\s");
        final Pattern p2 = Pattern.compile("\\p{Space}");
        final Pattern p3 = Pattern.compile("\\p{javaWhitespace}");

        for (char ch = Character.MIN_VALUE; ch < Character.MAX_VALUE; ++ch) {
            if (p1.matcher(new String(new char[]{ch})).find()) {
                alP1.add(ch);
            }
            if (p2.matcher(new String(new char[]{ch})).find()) {
                alP2.add(ch);
            }
            if (p3.matcher(new String(new char[]{ch})).find()) {
                alP3.add(ch);
            }
        }

        final StringBuilder sb = new StringBuilder();

        sb.append("\\s").append('\n');
        alP1.forEach((c) -> appendChar(sb, c));

        sb.append('\n').append("\\p{Space}").append('\n');
        alP2.forEach((c) -> appendChar(sb, c));

        sb.append('\n').append("\\p{javaWhitespace}").append('\n');
        alP3.forEach((c) -> appendChar(sb, c));

        System.out.println(sb.toString().trim());
    }
}

輸出的第一個欄是字元的16進制字元值,第二個欄位是以UTF-8編碼之後的byte陣列,第三個欄位是以UTF-8編碼之後的無號int陣列。

執行結果如下(會根據Java版本不同而有所差異):

\s
\u0009 [9]                [9]
\u000a [10]               [10]
\u000b [11]               [11]
\u000c [12]               [12]
\u000d [13]               [13]
\u0020 [32]               [32]

\p{Space}
\u0009 [9]                [9]
\u000a [10]               [10]
\u000b [11]               [11]
\u000c [12]               [12]
\u000d [13]               [13]
\u0020 [32]               [32]

\p{javaWhitespace}
\u0009 [9]                [9]
\u000a [10]               [10]
\u000b [11]               [11]
\u000c [12]               [12]
\u000d [13]               [13]
\u001c [28]               [28]
\u001d [29]               [29]
\u001e [30]               [30]
\u001f [31]               [31]
\u0020 [32]               [32]
\u1680 [-31, -102, -128]  [225, 154, 128]
\u180e [-31, -96, -114]   [225, 160, 142]
\u2000 [-30, -128, -128]  [226, 128, 128]
\u2001 [-30, -128, -127]  [226, 128, 129]
\u2002 [-30, -128, -126]  [226, 128, 130]
\u2003 [-30, -128, -125]  [226, 128, 131]
\u2004 [-30, -128, -124]  [226, 128, 132]
\u2005 [-30, -128, -123]  [226, 128, 133]
\u2006 [-30, -128, -122]  [226, 128, 134]
\u2008 [-30, -128, -120]  [226, 128, 136]
\u2009 [-30, -128, -119]  [226, 128, 137]
\u200a [-30, -128, -118]  [226, 128, 138]
\u2028 [-30, -128, -88]   [226, 128, 168]
\u2029 [-30, -128, -87]   [226, 128, 169]
\u205f [-30, -127, -97]   [226, 129, 159]
\u3000 [-29, -128, -128]  [227, 128, 128]

從以上結果我們可以得知,\s\p{Space}是相同的,且\p{javaWhitespace}也確實就是Character.isWhitespace()

\s\p{Space}的匹配範圍只有:

  • 水平定位符號(TAB字元)\u0009(\t)。
  • 換行字元\u000A(\n)。
  • 垂直定位符號\u000B
  • 換頁字元\u000C
  • 回車字元\u000D(\r)。
  • 空白字元\u0020(包含在SPACE_SEPARATOR字元集中)。