1. 正则表达式是什么
菜鸟教程:正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
2. 正则语法
构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为”元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
普通字符
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
非打印字符

特殊字符
特殊字符就是具有特殊功能的字符,如果要匹配特殊字符本身,需要加上转义字符\。
| 特殊字符      |    描述 |
| :——– | ——–:|
| $  | 匹配行尾,如果设置了RegExp的MultiLine属性,则可以匹配多行,即匹配字符串尾 |
|^|匹配行首|
||匹配`号前面子表达式 0次或多次|
|+|匹配+号前面子表达式 1次或多次|
|.|匹配除换行符\n`以外的所有字符|
|[|标记一个中括号表达式的开始|
|{|标记限定符表达式的开始|
|()|标记一个子表达式的开始和结束|
|?|匹配前面子表达式0次或一次|
|||指明两项中间的一个选择|
限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
3. java怎么使用正则
正则表达式的最简单用法就是测试某个特定的字符串是否与它匹配。
- 首先构建一个Pattern对象。Pattern pattern = Pattern.compile(patternString)#patternString为正则表达式字符串
- 从pattern中获得一个Matcher,并调用它的matches方法Mathcer matcher = pattern.matcher(input)#input是需要匹配的字符串
 这个匹配器的输入和可以是任何实现了CharSequence接口的类的对象,例如String、StringBuffer和CharBuffer。
在编译这个模式时,你可以设置一个或多个标志:Pattern pattern = Pattern.compile(patternString, 标志1,标志2...)
下面是所支持的6个标志:
- CASE_INSENSITIVE:匹配字符时忽略字母的大小写。默认情况下这个标志只考虑US ASCII码字符。
- UNICODE_CASE:当与- CASE_INSENSITIVE组合时,用Unicode字符集的大小写来匹配。
- MULTILINE:^和$匹配行的开头和结尾,而不是整个输入的开头和结尾。
- UNIX_LINES:在多行模式中匹配^和$时,只有- \n被识别成行终止符。
- DOTALL:当使用这个标志时,- .符号匹配所有字符,包括行终止符。
- CANON_EQ:考虑Unicode字符规范的等价性。例如,u后面跟随分音符号匹配- 我打不出来的这个音标
如果正则表达式包含群组:
可以使用mather.group(int groupIndex)来获取某一个匹配群组的字符
但是这里需要注意,在调用这个函数前需要先调用另外Matcher类的两个函数中的一个:
- boolean matches()这个函数会尝试匹配整个字符串,而不是字符串的某一个部分,只有整个字符串匹配上了才会返回- true
- boolean find()这个函数就会尝试匹配所有情况,而不是整个字符串都要完全匹配。
 【对比】- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- package re; 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 /**
 * author: jifang
 * date: 18-3-23 上午9:57
 */
 public class RegexTest {
 public static void main(String[] args) {
 Pattern pattern = Pattern.compile("(1?[0-9]):([0-5][0-9])[ap]m");
 String input = "11:59am11:59am";
 Matcher matcher = pattern.matcher(input);
 if (matcher.matches()){
 System.out.println("matches方法:" + matcher.group(0));
 }else {
 System.out.println("matche方法没有匹配到");
 }
 if (matcher.find()) {
 System.out.println("find方法:" + matcher.group(0));
 }else {
 System.out.println("find方法没有匹配到");
 }
 }
 }
Matcher类的replaceAll方法将正则表达式出现的所有地方都用替换字符串来替换。
“替换字符串可以包含对模式中group的引用:$n表示替换成第n个group。” 这句话的意思是把所有满足匹配的group都替换成第n个group。当我们需要在替换文本中包含一个$字符时,需要使用\$来表示。
如果字符串中包含$和\,但是又不希望他们被解释成群组的替换符,那么就可以调用matcher.replaceAll(Matcher.quoteReplacement(str))。
这里给一个题目,是我在查询Matcher.replaceAll方法时浏览到的一个博客上的题目,大家有兴趣可以自己先写一下,并不难,但是在解这个题的时候基本上关于java 正则相关的几个类的函数你都会用到,其中有很多坑,尤其是find()函数:
【题目】:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15字符串模板: 
    String template="尊敬的客户${customerName}你好!本次消费金额${amount},您帐户${accountNumber}上的余额为${balance},欢迎下次光临!"; 
其中以 ${ 开始 } 结尾的为待替换的变量域。 
数据存放于Map中,key为域名,value为域值。如: 
Map-- 
    customerName = 刘明 
    accountNumber = 888888888
    balance = $1000000.00
    amount = $1000.00 
请编写函数: 
    public static String composeMessage(String template, Map data) throw Exception 
实现将任意模板字符串中的变量域,按域名替换为data中的域值。 
例如,上例替换结果为: 
    "尊敬的客户刘明你好!本次消费金额$1000.00,您帐户888888888上的余额为$1000000.00,欢迎下次光临!" 
注:如果Map中找不到域值,以空字符串""替换。
【我的解法】1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43package re;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * author: jifang
 * date: 18-3-23 上午9:57
 */
public class RegexTest {
    public static void main(String[] args) throws Exception {
        String template="尊敬的客户${customerName}你好!本次消费金额${amount},您帐户${accountNumber}上的余额为${balance},欢迎下次光临!";
        Map<String,String> map = new HashMap<>();
        map.put("customerName","刘明");
        map.put("accountNumber","888888888");
        map.put("balance","$1000000.00");
        map.put("amount","$1000.00");
        String output = composeMessage(template, map);
        System.out.println(output);
    }
    public static String composeMessage(String template, Map data)throws Exception{
        Pattern pattern = Pattern.compile("(\\$\\{(\\w+?)\\})");
        Matcher matcher = pattern.matcher(template);
        String result = template;
        // 一开始我使用的groupCount函数进行for循环替换,但是没想到返回的结果一直是1。后来试了好几次才明白,find()函数每次只会返回一个匹配的最大group,为什么是最大group呢,也就是说满足compile函数中的正则表达式的大字符串,而在正则表达式中的小的一些group是会一起返回的。
        while (matcher.find()){
                String key = matcher.group(2);
                String value = (String) data.get(key);
                if (value==null){
                    value="";
                }
                else if (value.contains("$")){
                    value = "\\"+value;
                }
                result = result.replaceFirst("\\$\\{[a-zA-Z]+?\\}",value);
        }
        return result;
    }
}
【原博主答案】1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExam {
    public static void main(String args[]) {
        HashMap data = new HashMap();
        String template = "尊敬的客户${customerName}你好!本次消费金额${amount},"
                + "您帐户${accountNumber}上的余额为${balance},欢迎下次光临!";
        data.put("customerName", "刘明");
        data.put("accountNumber", "888888888");
        data.put("balance", "$1000000.00");
        data.put("amount", "$1000.00");
        try {
            System.out.println(composeMessage(template, data));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static String composeMessage(String template, Map data)
            throws Exception {
        String regex = "\\$\\{(.+?)\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(template);
        /*
         * sb用来存储替换过的内容,它会把多次处理过的字符串按源字符串序
         * 存储起来。
         */
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String name = matcher.group(1);//键名
            String value = (String) data.get(name);//键值
            if (value == null) {
                value = "";
            } else {
                /*
                 * 由于$出现在replacement中时,表示对捕获组的反向引用,所以要对上面替换内容
                 * 中的 $ 进行替换,让它们变成 "\$1000.00" 或 "\$1000000000.00" ,这样
                 * 在下面使用 matcher.appendReplacement(sb, value) 进行替换时就不会把
                 * $1 看成是对组的反向引用了,否则会使用子匹配项值amount 或 balance替换 $1
                 * ,最后会得到错误结果:
                 *
                 * 尊敬的客户刘明你好!本次消费金额amount000.00,您帐户888888888上的余额
                 * 为balance000000.00,欢迎下次光临!
                 *
                 * 要把 $ 替换成 \$ ,则要使用 \\\\\\& 来替换,因为一个 \ 要使用 \\\ 来进
                 * 行替换,而一个 $ 要使用 \\$ 来进行替换,因 \ 与  $ 在作为替换内容时都属于
                 * 特殊字符:$ 字符表示反向引用组,而 \ 字符又是用来转义 $ 字符的。
                 */
                value = value.replaceAll("\\$", "\\\\\\$");
                //System.out.println("value=" + value);
            }
            /*
             * 经过上面的替换操作,现在的 value 中含有 $ 特殊字符的内容被换成了"\$1000.00"
             * 或 "\$1000000000.00" 了,最后得到下正确的结果:
             *
             * 尊敬的客户刘明你好!本次消费金额$1000.00,您帐户888888888上的
             * 余额为$1000000.00,欢迎下次光临!
             *
             * 另外,我们在这里使用Matcher对象的appendReplacement()方法来进行替换操作,而
             * 不是使用String对象的replaceAll()或replaceFirst()方法来进行替换操作,因为
             * 它们都能只能进行一次性简单的替换操作,而且只能替换成一样的内容,而这里则是要求每
             * 一个匹配式的替换值都不同,所以就只能在循环里使用appendReplacement方式来进行逐
             * 个替换了。
             */
            matcher.appendReplacement(sb, value);
            System.out.println("sb = " + sb.toString());
        }
        //最后还得要把尾串接到已替换的内容后面去,这里尾串为“,欢迎下次光临!”
        matcher.appendTail(sb);
        return sb.toString();
    }
}
好了,正则表达式就到这里,下次见。
参考文献
[1] string.replaceAll()中的特殊字符($ \)与matcher.appendReplacement
[2] 《Java核心技术卷二》
[3] 菜鸟教程-java正则表达式
 
		 
                      