2017年8月17日 星期四

正則表達式

可看 java api 的 java.util.regex 套件裡的 Pattern


※^

表示開頭的意思,但在方括號裡是相反的意思,如[^\d],表示非數字,相當於 [\D] 或 [^0-9]


※\b

System.out.println("hello".matches("he\\b.*")); // false
System.out.println("hello".matches(".*lo\\b.*")); // true
System.out.println("follow".matches(".*lo\\b.*")); // false
     
System.out.println("hellow".matches(".*lo\\b.*")); // false

※控制是不是整個單字

※重點是第二個和第四個
第二個雖然是.*結尾,但已經控制成任何字元的 lo 結尾,所以第四行只是多個 w,就過不了



※\Q~\E

final String pipe = "|";
System.out.println(pipe.matches("\\|"));
System.out.println(pipe.matches("\\Q|\\E"));
System.out.println(pipe.matches(Pattern.quote("|")));
    
final String slash = "\\";
System.out.println(slash.matches("\\\\"));
System.out.println(slash.matches("\\Q\\\\E"));
System.out.println(slash.matches(Pattern.quote("\\")));

※使用 \Q \E 包住的字,可使裡面的字變成普通的字串

※也可用 Pattern.quote



※\A \Z \z

String matcherStr = "This is Java\r\nThis is a Java\r\n";
Matcher m = Pattern.compile("Java\\Z", Pattern.MULTILINE).matcher(matcherStr);// //AThis、Java//Z
    
while (m.find()) {
    System.out.println(m.group());
    System.out.println(m.start());
    System.out.println(m.end());
    System.out.println();
}

※^$第一行的開頭結尾,如果是多行模式,就是每行的開頭結尾

※\A、\Z、\z 不會管多行模式,可以說是文章的開頭和結尾

※\Z結束是終止符號時(如\r\n…等),也會匹配,但\z不行

※Pattern.compile 的第二個參數不加又想啟用多行模式時,可用(?m) 寫在第一個參數最前面或最後面
而 (?i) ,可代替 Pattern.CASE_INSENSITIVE
兩個都用就用 (?im) 或 (?mi)



※\1~\9

System.out.println("1_aa11a".matches("(\\d)_(\\w)\\2\\1\\1\\2"));

※數字表示從左到右第幾個圓括號

※第1個圓括號是數字,假設輸入8,那後面的 \1 ,也就一定要 8 才會過

※括號是從左到右,所以不能下如:(\\d)\\2(\\w)\\1,此時\\2在第二個圓括號前面,但這時第二個圓括號還沒出生,所以並沒有成功



※中文

\u4e00-\u9fa5
可以在執行打 charmap,可以看到是從「一」到「龥」,跟字典一樣

※可以看出來,是從左到右,然後上到下開始編。但如上圖,還是可以看出來「一」前面的和「龥」後面的漢字並沒有包括進去,所以
System.out.println("龦".matches("[\\u4e00-\\u9fa5]")); 為 false
但這個字很奇怪,明明畫面是「火+常」,但複製後居然是「火+堂」,這是用windows 的 chrome 電腦看的,但用手機的又是「火+常」
這張圖選的是細明體,選別的不一定會有一樣的字,因為選其他的字體,有可能沒做某些字的字體,所以如果選西歐語系的,通常就不會做中文的

※以下列出 java 版本和 unicode 的對應,java api 裡的 Character有寫
java6 4.0.0
java7 6.0.0,左下有個 Related Links,子項的 Unicode Character Name Index 可點進去,第四版我沒找到
java8 6.2.0
java9 和10 8.0.0
java11 10.0.0
java12 11.0.0
java api 看 Character 裡面會寫 unicode 的版本

中文歸類在 CJK 裡,China、Japan、Korean 的意思
由於 Unicode 使用 ASCII 時都是 2B,這樣容量就多一倍了,所以才會有轉換格式,如 UTF-8 (Unicode Transfer Format 8bits),轉換轉的是 ASCII (2 的 7 次方),不是 extend ASCII (2 的 8 次成),ASCII 二進位都是 0 開頭,extend ASCII 容量在 UTF-8 都是 2B

UTF-16 又分成 UTF16BE、UTF16LE,Macintosh (Mac) 電腦和其他電腦對 UTF-16 是不同的解譯,所以才有了 BE 和 LE

※Unicode 轉 UTF-8

0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0020 0000-03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0400 0000-7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

1.「|」的左邊為 Unicode (16進制);右邊為 UTF-8 (2進制),維基可找到這個表,但 java 不支援最後兩行,因為Character API 有說明,只有到 10FFFF,再加 1 直接報錯
2.0 開頭可以對應 ASCII,所以容量和 ISO-8859-1 沒區別
3.剩下的都是以 1 為開頭,以 1 為開頭的除了第一個 Byte 以外,其他都是 10 開頭
4.第 1 個 Byte 的 1 有幾個,就代表這個字有幾 Byte

以「搴」為例,在 charmap 找到的 unicode 為 6434
6434 轉 2 進制為:
0110 0100 0011 0100
以上面的面 x 有 16 個的,只有第 3 行,所以將這些值填進去後的結果為:
1110 0110 1001 0000 1011 0100,再轉成 16 進制變成:
E6 90 B4


※驗證:

方法一:
String s = Integer.toHexString("搴".charAt(0));
System.out.println(s); // 6434
System.out.println((char) Integer.parseInt(s, 16)); // 搴

強轉成 char,char 只有 2 的 16 次方,超過會自動 mod 65536 (也就是一直減 65536,減到在 65536 裡的範圍) 此種方法只有在 65536 裡的字有效,所以這種方法最多只有 3B 的字

方法二:
String x = "\uD840\uDC13"; //𠀓,貼到 String 裡,IDE 自動變成 \u 開頭了「※ \u 在註解裡執行時也會報錯」
int unicodeTen = x.codePointAt(0); // 131091
System.out.println(new StringBuilder().appendCodePoint(unicodeTen).toString());


System.out.println(new String(Character.toChars(unicodeTen)));
可用以上兩種方法轉出超過 65536 的字元

※131091 的公式是 (\uD840 << 10) + \uDC13 - 56613888‬
56688640 + 56339 - 56613888 = 131091
最多到 \uDBFF\uDFFF 就是 10FFFF 了

※Unicode 、 UTF-8、UTF-16 各佔幾個 Byte

System.out.println(Charset.defaultCharset()); // UTF-8
for (byte b : "搴".getBytes()) {
System.out.println(Integer.toHexString(b & 255));
}

System.out.println("---------------------");
for (byte b : "搴".getBytes("Unicode")) {
System.out.println(Integer.toHexString(b & 255));
}
for (byte b : "\uD840\uDC13".getBytes(StandardCharsets.UTF_16)) {
System.out.println(Integer.toHexString(b & 255));
}

Charset.defaultCharset 要看你的系統是什麼,如果是 UTF-8 ,使用 length 沒問題
使用 Unicode、UTF-16 長度必需減 2,因為最前面會有 FEFF 開頭,為 BOM



※超過 FFFF

int x = 0x10FFFF; // Character API 說最多到這裡
System.out.println(new String(Character.toChars(x)));

String y = new StringBuilder().appendCodePoint(0xffff).toString();
System.out.println(y);

沒辦法用 char 去接了,所以就用以上的兩種方法之一即可
要有安裝相應的字體才能顯示正確,我看字元對應表最多只能顯示 FFFF 的樣子
超過的就是上表的 4~6 行了,會佔 4~6 Byte
這個符號「𒈬」在 unicode 和 utf-8 分別就是 3、4 Byte了,可用上面的程式 FFFF 去跑迴圈並印出來就能看到了,有可能沒安裝字體看不到,可到 https://www.google.com/get/noto/ 下載,我只有安裝 (NotoSansCJKsc-Black.otf) Noto Sans CJK SC 黑色


𠀓 (引下面還有一條線)就超過了
如果看不到,可安裝完上面的字體後到 https://www.unicode.org/charts/PDF/U20000.pdf 隨便複製一個試試看就知道了,可配合我這篇驗證,維基也有將 Unicode 做分類,java 則在 Character 類別的 blockStarts[]

StringBuilder sb = new StringBuilder();
Stream.iterate(0, x -> x).limit(1000).reduce((i, j) -> {
sb.appendCodePoint(131100 + i);
return ++i;
});
System.out.print(sb);

列出一部分超過 3B 的中文,數字是 10 進制,也可以用 16 進制或其他的,如 0x2001C,最多只到 10FFFF,再加 1 就直接報錯了,並不會 mod

以下是結果:
𠀜𠀝𠀞𠀟𠀠𠀡𠀢𠀣𠀤𠀥𠀦𠀧𠀨𠀩𠀪𠀫𠀬𠀭𠀮𠀯𠀰𠀱𠀲𠀳𠀴𠀵𠀶𠀷𠀸𠀹𠀺𠀻𠀼𠀽𠀾𠀿𠁀𠁁𠁂𠁃𠁄𠁅𠁆𠁇𠁈𠁉𠁊𠁋𠁌𠁍𠁎𠁏𠁐𠁑𠁒𠁓𠁔𠁕𠁖𠁗𠁘𠁙𠁚𠁛𠁜𠁝𠁞𠁟𠁠𠁡𠁢𠁣𠁤𠁥𠁦𠁧𠁨𠁩𠁪𠁫𠁬𠁭𠁮𠁯𠁰𠁱𠁲𠁳𠁴𠁵𠁶𠁷𠁸𠁹𠁺𠁻𠁼𠁽𠁾𠁿𠂀𠂁𠂂𠂃𠂄𠂅𠂆𠂇𠂈𠂉𠂊𠂋𠂌𠂍𠂎𠂏𠂐𠂑𠂒𠂓𠂔𠂕𠂖𠂗𠂘𠂙𠂚𠂛𠂜𠂝𠂞𠂟𠂠𠂡𠂢𠂣𠂤𠂥𠂦𠂧𠂨𠂩𠂪𠂫𠂬𠂭𠂮𠂯𠂰𠂱𠂲𠂳𠂴𠂵𠂶𠂷𠂸𠂹𠂺𠂻𠂼𠂽𠂾𠂿𠃀𠃁𠃂𠃃𠃄𠃅𠃆𠃇𠃈𠃉𠃊𠃋𠃌𠃍𠃎𠃏𠃐𠃑𠃒𠃓𠃔𠃕𠃖𠃗𠃘𠃙𠃚𠃛𠃜𠃝𠃞𠃟𠃠𠃡𠃢𠃣𠃤𠃥𠃦𠃧𠃨𠃩𠃪𠃫𠃬𠃭𠃮𠃯𠃰𠃱𠃲𠃳𠃴𠃵𠃶𠃷𠃸𠃹𠃺𠃻𠃼𠃽𠃾𠃿𠄀𠄁𠄂𠄃𠄄𠄅𠄆𠄇𠄈𠄉𠄊𠄋𠄌𠄍𠄎𠄏𠄐𠄑𠄒𠄓𠄔𠄕𠄖𠄗𠄘𠄙𠄚𠄛𠄜𠄝𠄞𠄟𠄠𠄡𠄢𠄣𠄤𠄥𠄦𠄧𠄨𠄩𠄪𠄫𠄬𠄭𠄮𠄯𠄰𠄱𠄲𠄳𠄴𠄵𠄶𠄷𠄸𠄹𠄺𠄻𠄼𠄽𠄾𠄿𠅀𠅁𠅂𠅃𠅄𠅅𠅆𠅇𠅈𠅉𠅊𠅋𠅌𠅍𠅎𠅏𠅐𠅑𠅒𠅓𠅔𠅕𠅖𠅗𠅘𠅙𠅚𠅛𠅜𠅝𠅞𠅟𠅠𠅡𠅢𠅣𠅤𠅥𠅦𠅧𠅨𠅩𠅪𠅫𠅬𠅭𠅮𠅯𠅰𠅱𠅲𠅳𠅴𠅵𠅶𠅷𠅸𠅹𠅺𠅻𠅼𠅽𠅾𠅿𠆀𠆁𠆂𠆃𠆄𠆅𠆆𠆇𠆈𠆉𠆊𠆋𠆌𠆍𠆎𠆏𠆐𠆑𠆒𠆓𠆔𠆕𠆖𠆗𠆘𠆙𠆚𠆛𠆜𠆝𠆞𠆟𠆠𠆡𠆢𠆣𠆤𠆥𠆦𠆧𠆨𠆩𠆪𠆫𠆬𠆭𠆮𠆯𠆰𠆱𠆲𠆳𠆴𠆵𠆶𠆷𠆸𠆹𠆺𠆻𠆼𠆽𠆾𠆿𠇀𠇁𠇂𠇃𠇄𠇅𠇆𠇇𠇈𠇉𠇊𠇋𠇌𠇍𠇎𠇏𠇐𠇑𠇒𠇓𠇔𠇕𠇖𠇗𠇘𠇙𠇚𠇛𠇜𠇝𠇞𠇟𠇠𠇡𠇢𠇣𠇤𠇥𠇦𠇧𠇨𠇩𠇪𠇫𠇬𠇭𠇮𠇯𠇰𠇱𠇲𠇳𠇴𠇵𠇶𠇷𠇸𠇹𠇺𠇻𠇼𠇽𠇾𠇿𠈀𠈁𠈂𠈃𠈄𠈅𠈆𠈇𠈈𠈉𠈊𠈋𠈌𠈍𠈎𠈏𠈐𠈑𠈒𠈓𠈔𠈕𠈖𠈗𠈘𠈙𠈚𠈛𠈜𠈝𠈞𠈟𠈠𠈡𠈢𠈣𠈤𠈥𠈦𠈧𠈨𠈩𠈪𠈫𠈬𠈭𠈮𠈯𠈰𠈱𠈲𠈳𠈴𠈵𠈶𠈷𠈸𠈹𠈺𠈻𠈼𠈽𠈾𠈿𠉀𠉁𠉂𠉃𠉄𠉅𠉆𠉇𠉈𠉉𠉊𠉋𠉌𠉍𠉎𠉏𠉐𠉑𠉒𠉓𠉔𠉕𠉖𠉗𠉘𠉙𠉚𠉛𠉜𠉝𠉞𠉟𠉠𠉡𠉢𠉣𠉤𠉥𠉦𠉧𠉨𠉩𠉪𠉫𠉬𠉭𠉮𠉯𠉰𠉱𠉲𠉳𠉴𠉵𠉶𠉷𠉸𠉹𠉺𠉻𠉼𠉽𠉾𠉿𠊀𠊁𠊂𠊃𠊄𠊅𠊆𠊇𠊈𠊉𠊊𠊋𠊌𠊍𠊎𠊏𠊐𠊑𠊒𠊓𠊔𠊕𠊖𠊗𠊘𠊙𠊚𠊛𠊜𠊝𠊞𠊟𠊠𠊡𠊢𠊣𠊤𠊥𠊦𠊧𠊨𠊩𠊪𠊫𠊬𠊭𠊮𠊯𠊰𠊱𠊲𠊳𠊴𠊵𠊶𠊷𠊸𠊹𠊺𠊻𠊼𠊽𠊾𠊿𠋀𠋁𠋂𠋃𠋄𠋅𠋆𠋇𠋈𠋉𠋊𠋋𠋌𠋍𠋎𠋏𠋐𠋑𠋒𠋓𠋔𠋕𠋖𠋗𠋘𠋙𠋚𠋛𠋜𠋝𠋞𠋟𠋠𠋡𠋢𠋣𠋤𠋥𠋦𠋧𠋨𠋩𠋪𠋫𠋬𠋭𠋮𠋯𠋰𠋱𠋲𠋳𠋴𠋵𠋶𠋷𠋸𠋹𠋺𠋻𠋼𠋽𠋾𠋿𠌀𠌁𠌂𠌃𠌄𠌅𠌆𠌇𠌈𠌉𠌊𠌋𠌌𠌍𠌎𠌏𠌐𠌑𠌒𠌓𠌔𠌕𠌖𠌗𠌘𠌙𠌚𠌛𠌜𠌝𠌞𠌟𠌠𠌡𠌢𠌣𠌤𠌥𠌦𠌧𠌨𠌩𠌪𠌫𠌬𠌭𠌮𠌯𠌰𠌱𠌲𠌳𠌴𠌵𠌶𠌷𠌸𠌹𠌺𠌻𠌼𠌽𠌾𠌿𠍀𠍁𠍂𠍃𠍄𠍅𠍆𠍇𠍈𠍉𠍊𠍋𠍌𠍍𠍎𠍏𠍐𠍑𠍒𠍓𠍔𠍕𠍖𠍗𠍘𠍙𠍚𠍛𠍜𠍝𠍞𠍟𠍠𠍡𠍢𠍣𠍤𠍥𠍦𠍧𠍨𠍩𠍪𠍫𠍬𠍭𠍮𠍯𠍰𠍱𠍲𠍳𠍴𠍵𠍶𠍷𠍸𠍹𠍺𠍻𠍼𠍽𠍾𠍿𠎀𠎁𠎂𠎃𠎄𠎅𠎆𠎇𠎈𠎉𠎊𠎋𠎌𠎍𠎎𠎏𠎐𠎑𠎒𠎓𠎔𠎕𠎖𠎗𠎘𠎙𠎚𠎛𠎜𠎝𠎞𠎟𠎠𠎡𠎢𠎣𠎤𠎥𠎦𠎧𠎨𠎩𠎪𠎫𠎬𠎭𠎮𠎯𠎰𠎱𠎲𠎳𠎴𠎵𠎶𠎷𠎸𠎹𠎺𠎻𠎼𠎽𠎾𠎿𠏀𠏁𠏂𠏃𠏄𠏅𠏆𠏇𠏈𠏉𠏊𠏋𠏌𠏍𠏎𠏏𠏐𠏑𠏒𠏓𠏔𠏕𠏖𠏗𠏘𠏙𠏚𠏛𠏜𠏝𠏞𠏟𠏠𠏡𠏢𠏣𠏤𠏥𠏦𠏧𠏨𠏩𠏪𠏫𠏬𠏭𠏮𠏯𠏰𠏱𠏲𠏳𠏴𠏵𠏶𠏷𠏸𠏹𠏺𠏻𠏼𠏽𠏾𠏿𠐀𠐁𠐂

String s = "\uD840\uDCE6";
System.out.println(s.length()); // 2
System.out.println(s.codePointCount(0, s.length())); // 1
因為以上的原故,所以長度 length 就不一定是1了,要改用 codePointCount


※UTF-16

U+0000---U+FFFF xxxxxxxx xxxxxxxx yyyyyyyy yyyyyyyy 0-65535
U+10000---U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111
第一條佔 2Byte,第二條佔 4Byte
java 的 Character 和 String 使用的是 UTF-16,當英文字多時,用 UTF-8 較節省,因為 UTF-8 的前三行在 UTF-16 都是 2B,空檔案也是 2B

𠀓
D840  DC13  16 進制直接轉 2 進制就好了
1101 1000 0100 0000
1101 1100 0001 0011


6434
0110 0100 0011 0100


UTF-16 還有 BE 和 LE

每個文件開頭有個看不見的 BOM,FEFF,程式一讀到就知道是 UTF-16了
以 D840  DC13 來說,假設 BE 為 FEFF D840  DC13
那 LE 會以 16 進制來調換,變成 FFFE 40D8 13DC
big endian 小 -> 大,所以是 FEFF
little endian 大 -> 小,所以是 FFFE


UTF-32

全部都是 4B


javascript 

let unicodeTen = '\uDBFF\uDFFF'.codePointAt();
console.log(unicodeTen); // 1114111
console.log(unicodeTen.toString(16)); // 10ffff
console.log(String.fromCodePoint(0x10ffff));
js 和 java 完全一樣,內部也是用 UTF-16

※其他編碼細節,可看這篇文章



※判斷中日韓

private static boolean isCJK3Byte(char c) {
    Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
    List<Character.UnicodeBlock> chineseRange = List.of(
        Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT, // 2E80-2EFF 中日韓漢字部首補充
        Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION, // 3000-303F 中日韓符號和標點 (全形空格是 3000)
        Character.UnicodeBlock.CJK_STROKES, // 31C0-31EF 中日韓筆畫
        Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS, // 3200-32FF 帶圈的中日韓字符及月份 (有中文的為 3220-3247、3280-32B0、32C1-32CB、32FF)
        Character.UnicodeBlock.CJK_COMPATIBILITY, // 3300-33FF 中日韓兼容字符 (有中文的為 3358-3370、337B-337F、33E0-33FE)
        Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A, // 3400-4DBF 中日韓一表意文字擴展區A
        Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS, // 4E00-9FFF 中日韓統一表意文字
        Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS, // F900-FAFF 中日韓兼容表意文字
        Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS, // FE30-FE4F 中日韓兼容形式 (裡面是一些括號)
        Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS); // FF00-FFEF 半形及全形字符 (ASCII 從!到~符號的全形在 FF01-FF5E)
    
    return chineseRange.contains(ub);
}
    
private static boolean isCJK4Byte(String str) {
    if (str == null || str.strip().length() != 2) {
        return false;
    }
    String codePoint = String.valueOf(str.codePointAt(0));
    System.out.println("cp=" + codePoint);
    List<Pattern> listPattern = List.of(
            Pattern.compile("[\\u20000-\\u2A6DF]"), // 中日韓一表意文字擴展區B
            Pattern.compile("[\\u2A700-\\u2B73F]"), // 中日韓一表意文字擴展區C
            Pattern.compile("[\\u2B740-\\u2B81F]"), // 中日韓一表意文字擴展區D
            Pattern.compile("[\\u2B820-\\u2CEAF]"), // 中日韓一表意文字擴展區E
            Pattern.compile("[\\u2CEB0-\\u2EBEF]"), // 中日韓一表意文字擴展區F
            Pattern.compile("[\\u2F800-\\u2FA1F]")); // 中日韓兼容表意文字增補
    
    for (Pattern p : listPattern) {
        if (p.matcher(codePoint).find()) {
            return true;
        }
    }
    return false;
}


※因為 Character.UnicodeBlock.of 只接受 char,所以只能判斷 3Byte 的部分,4Byte 只好用正則了



※在 Windows 看 Big5 和 Unicode



切換到注音輸入法,然後出現「中」或「英」按右鍵有輸入法整合器
我的 Windows 8 要先開啟控制台,按右鍵才有反應


選左邊的「符」,然後上面的下拉可以選適合的,滑鼠移到符號上就有提示了
但下拉選項沒看到中文的,只好到全字庫查詢了


※小技巧:

切換到注音輸入法的中文模式,然後到可以輸入的地方
打上「`」 (鍵盤左上,上往下第二個按鍵), 再打上 [ubp] 的其中一個
u 表示 unicode
b 表示 big5
p 表示自己曾經自己定義過的符號
此例為 的 unicode 為 0x2199; big5 為 0xA1FA
打 `u2199 然後空格即可
打 `bA1FA 不用空格,自己會跳出符號
以上的按鍵都要在鍵盤的左邊打才行,不能使用鍵盤最右邊的數字鍵盤


※貪婪、勉強、完全匹配

※貪婪 (Greedy quantifiers):預設的模式,一次讀全部的字串,沒匹配就刪除一個字符再次匹配,如此循環,有匹配到就返回
X?
X*
X+
X{n}
X{n,}
X{n,m}

※勉強 (Reluctant quantifiers):在預設模式的最後加上「?」即可,一次讀一個字符,沒匹配到就增加一個字符,如此循環,有匹配到就返回
X??
X*?
X+?
X{n}?
X{n,}?
X{n,m}?

※完全匹配 (Possessive quantifiers):在預設模式的最後加上「+」即可,此模式只會跑一次,一次讀全部的字串,有匹配到就返回
X?+
X*+
X++
X{n}+
X{n,}+
X{n,m}+

※範例一

StringBuilder html = new StringBuilder();
html.append("<table>");
    
html.append("<tr>");
html.append("<td>aaa</td>");
html.append("<td>bbb</td>");
html.append("<td>ccc</td>");
html.append("</tr>");
    
html.append("<tr>");
html.append("<td>ddd</td>");
html.append("<td>eee</td>");
html.append("<td>fff</td>");
html.append("</tr>");
    
html.append("</table>");
    
Matcher m = Pattern.compile("<td>(.*)</td>").matcher(html);
while (m.find()) {
    System.out.println(m.group());
    System.out.println(m.start());
    System.out.println(m.end());
    System.out.println();
}

※貪婪
<td>aaa</td><td>bbb</td><td>ccc</td></tr><tr><td>ddd</td><td>eee</td><td>fff</td>
11
92

※非貪婪
<td>aaa</td>
11
23

<td>bbb</td>
23
35

<td>ccc</td>
35
47

<td>ddd</td>
56
68

<td>eee</td>
68
80

<td>fff</td>
80
92

※完全匹配
什麼都沒匹配到,因為「.*」會一直找到最後

※範例二

String html = "<ol><li>blog</li><li>book</li></ol>";
Matcher m = Pattern.compile("<li>(.{4,17})</li>").matcher(html);
while (m.find()) {
    System.out.println(m.group());
    System.out.println(m.start());
    System.out.println(m.end());
    System.out.println();
}

※貪婪
<li>Blog</li><li>book</li>
4
30

※非貪婪
<li>Blog</li>
4
17

<li>book</li>
17
30

※完全匹配
<li>Blog</li><li>book</li>
4
30


※捕獲組

final String html = "<ol><li title='a'>blog</li><li title='b'>book</li></ol>";
final String regexp = "(<li.*?>)(.*?)(</li>)";
Matcher m = Pattern.compile(regexp).matcher(html);
while (m.find()) {
    System.out.println(m.group(0)); // 和 m.group() 一樣
    System.out.println(m.group(1));
    System.out.println(m.group(2));
    System.out.println(m.group(3));
    System.out.println(m.start() + "," + m.end());
    System.out.println();
}
    
System.out.println(html.replaceFirst(regexp, "$2"));
System.out.println(html.replaceAll(regexp, "$2,"));

※就是補獲的字串是什麼,有幾個圓括號,group 就有幾個

※取代時,可用 $number 獲得補獲的內容

※結果:
<li title='a'>blog</li>
<li title='a'>
blog
</li>
4,27

<li title='b'>book</li>
<li title='b'>
book
</li>
27,50

<ol>blog<li title='b'>book</li></ol>
<ol>blog,book,</ol>



※非補獲組

非補獲的意思就是 matcher 的 group() 方法不會抓圓括號匹配到的值
也就是只分組而不捕獲

以「(?」開始,「)」結尾的為非捕獲組

※往前|後找(從這以下都很難懂,我這邊寫的也不一定是對的)

(?=X) X, via zero-width positive lookahead (往前找是 X 的)
(?!X) X, via zero-width negative lookahead (往後找不是 X 的)
(?<=X) X, via zero-width positive lookbehind (往後找是 X 的,也可想成左邊是 X 的)
(?<!X) X, via zero-width negative lookbehind (往前找不是 X 的,也可想成左邊不是 X 的)
指的是 (?=X) 的前後
<:往後看;沒有 < 就是往前看
!:不是
=:是

.零寬度:
例1:Pattern.compile("a(?=b)bc").matcher("abc")
1.先找到 b,然後看前面是不是 a
2.是 a,但因為是零寬度,所以只有 a
3.a 後面是 bc,所以以結果是 abc (0, 3)

例2:Pattern.compile("ab(?<=b)c").matcher("abc")
1.先找到左邊是 b 的,所以找到了 c
2.但因為是零寬度,所以只有 c
3.c 後面是 ab,所以以結果是 abc (0, 3)


.有 ! 的零寬度:! 為「不是」的意思
例1:Pattern.compile("a(?!b)").matcher("a")
1.找到 a 右邊的字是不是 b (此例只有一個 a,想像 a 右標的游標,所以不是 b),然後看前面是不是 a
2.是 a,但因為是零寬度,所以只有 a
3.結果為 a (0,1)

例2:Pattern.compile("a(?!b)c").matcher("ac")
1.找到 a 右邊的字是不是 b,看前面是不是 a
2.是 a,但因為是零寬度,所以只有 a
3..a 後面是 c,結果為 ac (0,2)


find(Pattern.compile("(?=yellow)").matcher("my dog is yellow and white")); // 10,10
System.out.println("######");
find(Pattern.compile("my dog is (?=yellow)").matcher("my dog is yellow and white")); // my dog is 0,10
    
System.out.println("######");
find(Pattern.compile("(?<=yellow)").matcher("my dog is yellow and white")); // 16,16
System.out.println("######");
find(Pattern.compile("(?<=yellow) and white").matcher("my dog is yellow and white")); //  and white 16,26
    
find(Pattern.compile("(?!yellow) and white").matcher("my dog is yellow and white")); // and white 16,26
find(Pattern.compile("my dog is (?<!yellow)").matcher("my dog is yellow and white")); //  my dog is 0,10
    
    
final String s = "0ab34ab789";
find(Pattern.compile("(?<!ab)").matcher(s)); // 0 1 2 4 5 6 8 9 10,左邊不是ab,注意有最後的 10,10
System.out.println("######");
find(Pattern.compile("(?!ab)").matcher(s)); // 0 2 3 4 6 7 8 9 10,不是ab,注意有最後的 10,10
    
    
private static void find(Matcher m) {
    while (m.find()) {
        System.out.println(m.group());
        System.out.println(m.start() + "," + m.end());
        System.out.println("----------");
    }
}

※數字代表 index,但要注意數字一樣的,如 10,10,也就是游標的位置也會算的



※名稱補獲

final String s = "0ab34ab789";
Matcher m = Pattern.compile("(?<xxx>.ab)").matcher(s);
while (m.find()) {
    System.out.println(m.group()); // 0ab、4ab
    System.out.println(m.group("xxx")); // 0ab、4ab
    System.out.println(m.start() + "," + m.end()); // 0,3、4,7
    System.out.println("----------");
}
//--------------------------------------------------------
Matcher m = Pattern.compile("(?<xxx>.ab)\\k<xxx>").matcher("0ab0ab789");
while (m.find()) {
    System.out.println(m.group()); // 0ab0ab
    System.out.println(m.group("xxx")); // 0ab
    System.out.println(m.start() + "," + m.end()); // 0,6
    System.out.println("----------");
}


※也就是在 ? 後面多個 <>,裡面取個名稱,在 java 可用 group 取抓到的值

※\k 專門用來配合名稱補獲的


※特殊符號補獲

find(Pattern.compile("(?i)aa(?-i)").matcher("aaAaAAaA")); // aa 0,2、Aa 2,4、AA 4,6、aA 6,8
System.out.println("######");
find(Pattern.compile("(?i:aa)cd").matcher("aaCDAaCdAAcDaAcd")); // aAcd 12,16
System.out.println("######");
final String s = "aaCDAaCdAAcDaAcd" +
    "aaCDAaCdAAcDaAcd" +
    "aaCDAaCdAAcDaAcd" +
    "aaCDAaCdAAcDaAcd";
    
find(Pattern.compile("(?im)aa(?-im)").matcher(s));
System.out.println("######");
find(Pattern.compile("(?im:aa)cd").matcher(s));
    
    
find(Pattern.compile("(?u)\u4e00(?-u)").matcher("一"));
System.out.println("######");
find(Pattern.compile("(?u:\u4e00)").matcher("一"));
System.out.println("######");
    
find(Pattern.compile("(?u)\u4e00(?-u)").matcher("\u4e00"));
System.out.println("######");
find(Pattern.compile("(?u:\u4e00)").matcher("\u4e00"));
System.out.println("######");
    
String s = "abc";
find(Pattern.compile("(?sx)a #b##.(?-sx)").matcher(s));
System.out.println("######");
find(Pattern.compile("(?sx:a b.)").matcher(s)); // 此種寫法,裡面不能寫 #,編譯會錯誤
System.out.println("######");
    
String s2 = "abc\ndef\nabc";
find(Pattern.compile("(?d)abc(?-d)").matcher(s2));
System.out.println("######");
find(Pattern.compile("(?d:abc)").matcher(s2));
System.out.println("######");
    
    
// find(Pattern.compile("\\p{Sc}").matcher("$\uFF04₿"));// FF04 是全型的 $
String s2 = "ab₿cde$f";
find(Pattern.compile("(?U)\\p{Sc}(?-U)").matcher(s2)); // ₿ 2,3、$ 6,7
System.out.println("######");
find(Pattern.compile("(?U:\\p{Sc})").matcher(s2)); // ₿ 2,3、$ 6,7



(?idmsuxU-idmsuxU) Nothing, but turns match flags i d m s u x U on - off
(?idmsux-idmsux:X)  X, as a non-capturing group with the given flags i d m s u x on - off

i 忽略 ASCII 的大小寫
d \n 會被當作一行的結束符號
m 多行模式
s dotall,就是「.」,可匹配任何字,包括結束符號
u 匹配 unicode
x 忽略表達式理的空格和 # 號
U Unicode character class,這個看不懂

※dmsuU,我試了沒加也可以,可能預設就有啟用了吧!

※unicode 超過 FFFF 會變成兩個字元,如「一」的結果為 0,1;但「𠀓」結果為 0,2,最多到 10FFFF

※\p 看 java API 裡的 Pattern 有說明,貨幣符號我只知道半全形的 $,和 Unicode 規定的,沒辦法,api 細節寫的都不清楚

※?:X、?>X

類似 ?= 但是沒有零寬度
想像成右邊是 X 的

find(Pattern.compile("(?=b)b").matcher("bb")); // b 0,1、b 1,2
System.out.println("######");
find(Pattern.compile("(?=b)b").matcher("bc")); // b 0,1
System.out.println("######");
find(Pattern.compile("(?:b)b").matcher("bb")); // bb 0,2
System.out.println("######");
find(Pattern.compile("(?:b)b").matcher("bc")); //

※以第二和第四個為例,找到 b 後,零寬度只能匹配到一個;非零寬度就能匹配到兩個



?:X、?>X 差異


find(Pattern.compile("a(?>bc|b)c").matcher("abc")); //
System.out.println("######");
find(Pattern.compile("a(?>bc|b)c").matcher("abcc")); // abcc 0,4
System.out.println("######");
find(Pattern.compile("a(?:bc|b)c").matcher("abc")); // abc 0,3
System.out.println("######");
find(Pattern.compile("a(?:bc|b)c").matcher("abcc")); // abcc 0,4


※?> 只會匹配一次,所以  bc|b 改成 b|bc 就能匹配上,類似上面的完全匹配


沒有留言:

張貼留言