你去爬山被告知的第一件事,就是别去看顶峰。而要专注于在爬的路,一步一个脚印地,耐心攀登,如果你不断看山顶,就会泄气。
前言
这是很早时候的文章了,当初学习Java时候整理的文章。
正则表达式
为了实现复杂的模式匹配,模式串的构造应该遵循某种规则,这样的模式称为正则表达式(regular expression)。
在Java语言中通过java.util.regex包中的类实现正则表达式的模式匹配。该包中提供了Pattern、Matcher和PatternSyntaxException类与MatchResult接口。
Pattern对象就是一个正则表达式的编译表示形式。Pattern类没有定义构造方法,要得到该类对象,需要调用该类的静态方法compile()。该方法接收一个字符串参数,该字符串必须是一个正则表达式,该方法返回Pattern对象。
Matcher类的实例用来按照给定的模式匹配一个输入字符序列。Matcher类也没有定义public的构造方法,需要调用Pattern类的实例方法matcher()返回Matcher类的一个实例,该方法接收一个字符序列,它就是模式要与其匹配的输入串。接下来,调用Matcher类的实例方法matches()来判断是否有一个匹配。
/**
* 从一个字符串中提取数字出来,add到price中
* 姓名:张三,数学72分,物理67分,英语80分.
* 姓名:张四,数学82分,物理68分,英语90分.
* 姓名:张五,数学92分,物理69分,英语91分.
* 姓名:张六,数学62分,物理61分,英语81分.
* */
private int getScore(String string) {
List<String> price = new ArrayList<>();
Pattern pattern = Pattern.compile("-*\\d+(\\.\\d+)?");
Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
price.add(matcher.group());
}
int result = 0;
for (String temp : price) {
result += Integer.parseInt(temp);
}
return result;
}
上面的代码,-*\\d+(\\.\\d+)?
是一个正则表达式,表达的意思就是从一个字符串中将是数字的提取出来(包括正负数,整数,小数)。
当正则表达式只使用一次的时候,使用Pattern类中定义的matches方法更加方便,该方法同时编译模式并与输入字符串进行匹配,如下语句:
boolean b = Pattern.matches(".*apple.*", "a1234apple567890");
Pattern类的实例是不可变的对象并且在被多个线程使用的使用是线程安全的,而Matcher实例不是安全的。
模式的指定
最简单的模式可以包含一个字符。要指定多个字符或者字符范围,需要使用方括号将字符括起来。
正则表达式 | 说明 |
---|---|
[abc] | 匹配a、b、c |
[^abc] | 匹配除了a、b、c以外的所有字符 |
[a-zA-Z] | 匹配az和AZ的字符 |
[a-d[m-p]] | ad或者mp(并) |
[a-z&&[def]] | d、e或者f(交) |
[a-z&&[^bc]] | 除了b、c以外a~z:[ad-z](差) |
[a-z&&[^m-p]] | a~z中除了m~p以外的字符:[a-lq-z](差) |
在模式串中还可以使用一些预定义的字符,称为元字符。
元字符 | 说明 |
---|---|
. | 匹配任何单个字符 |
\d | 一个数字:[0-9] |
\D | 一位非数字:[^0-9] |
\s | 空格字符:[\t\n\x0B\f\r] |
\S | 非空格字符:[^\s] |
\w | 一个单词字符:[a-zA-Z_0-9] |
\W | 一个非单词字符:[^\w] |
Java正则表达式类
这里简单的介绍下Java的正则表达式的部分API,方便查阅。
Pattern类
方法 | 说明 |
---|---|
public static Pattern compile(String regex) | 将给定的正则表达式编译成Pattern类的一个实例,如果表达式语法错误,将抛出PatternSyntaxException运行时异常。 |
public static Pattern compile(String regex, int flags) | flags参数用来指定匹配如何进行,它使用Pattern类的一个常量。 |
public String pattern() | 返回该模式对象的正则表达式字符串。 |
public Matcher matcher(CharSequence input) | 创建按照模式与给定的输入字符序列匹配的Matcher对象。 |
public static boolean matches(String regex, CharSequence input) | 对规定的正则表达式 编译并与输入序列匹配,如果成功则返回true。当不需要重复使用匹配器的时候可以使用这个方法。 |
public String[] split(CharSequence input) | 使用该模式对象对输入字符序列进行拆分。 |
public String[] split(CharSequence input, int limit) | limit参数用于控制模式使用的次数,它将影响结果数组的长度。 |
public int flags() | 返回该模式的匹配标志。 |
Matcher类
Matcher类的实例用来根据给定的模式匹配字符序列。通过调用模式对象的matcher方法来得到一个Matcher类的对象。
执行匹配的方法
方法 | 说明 |
---|---|
public boolean matches() | 尝试将整个输入序列与该模式进行匹配。如果返回true,就匹配成功,匹配成功之后就可以使用start方法、end方法和group方法获得更多的信息。 |
public boolean lookingAt() | 尝试从开始处的输入序列与该模式进行匹配。与matches方法不同的是它不需要匹配整个输入序列。 |
public boolean find() | 尝试查找输入序列中与该模式匹配的下一个序列的开头匹配,或者,如果上一次find方法是成功的,并且匹配器没有重置,则本次匹配从上次匹配中还没有进行匹配的第一个字符开始。 |
public boolean find(int start) | 重置该匹配器,然后尝试从制定索引位置开始查找与该模式匹配的下一个子序列。 |
返回匹配信息的方法
方法 | 说明 |
---|---|
public int start() | 返回上次成功匹配的开始索引值。 |
public int end() | 返回匹配的序列中最后一个字符的索引值加1的值。 |
public String group() | 返回匹配成功的子序列,即由start和end定义的子字符串。 |
public int groupCount() | 返回该匹配模式中的捕获数组。组号范围从0开始,到这个组数减1的值。 |
public String group(int group) | 返回上次匹配中与给定组匹配的输入子序列。第0组表示整个匹配模式,所以group(0)与group()等价。 |
public int start(int group) | 返回上次匹配中给定组的开始索引值。 |
public int end(int group) | 返回与给定组匹配的序列中最后一个字符的索引值加1。 |
改变匹配器状态的方法
方法 | 说明 |
---|---|
public Matcher reset() | 重置适配器,该方法将丢弃该匹配器的所有状态信息,并将其追加位置重置为0,该方法返回的Matcher对象就是调用该方法的Matcher对象。 |
public Matcher reset(CharSequence input) | 将匹配器重置为使用新的输入序列。 |
public Matcher usePattern(Pattern pattern) | 将该匹配器使用的模式重新设置为传递进来的模式pattern。 |
量词和捕获组
量词(quantifiers)用来制定模式在字符串中出现的次数。有三种类型的量词,贪婪(greedy)量词、勉强(reluctant)量词和具有(possessive)量词。
贪婪量词 | 勉强量词 | 具有量词 | 模式X出现的次数 |
---|---|---|---|
X? | X?? | X? + | X出现0次或者1次。 |
X* | X*? | X*+ | X出现0次或者多次。 |
X+ | X+? | X++ | X出现1次或者多次。 |
X{n} | X{n}? | X{n}+ | X恰好出现n次。 |
X{n, } | X{n, }? | X{n, }+ | X至少出现n次。 |
X{n, m} | X{n, m}? | X{n, m}+ | X至少出现n次,但不超过m次。 |
表的前三列给出了三种不同类型的量词,它们的用法不同。首先,来了解下元字符的含义。
最一般的量词就是{n, m},n和m都是整数。X{n, m}在字符串中X至少n次,但不超过m次。例如,X{3, 5}匹配的字符串包括XXX、XXXX和XXXXX,但是不包括X、XX和XXXXXX。
使用贪婪量词,匹配器首先将整个输入串读入。如果匹配失败,它将回退一个字符,检查匹配,知道没有字符为止。
使用勉强量词,匹配器首先读入输入串的一个字符。如果匹配失败,它将添加一个后续字符,然后再检查。重复该过程,知道读完所有字符为止。
使用具有量词与前两者不同,它使适配器读取整个输入串,然后停止。
下面的表格说明了三种量词的不同。输入串为“whellowwwhellowww”。
正则表达式 | 结果 |
---|---|
.*hello | 找到文本“whellowwwhello”,开始索引为0,结束索引为14。 |
.*?hello | 找到文本“whello”, 开始索引为0,结束索引6和文本“wwwhello”, 开始索引为6,结束索引为14。 |
.*+hello | 没有找到匹配。 |
说明,.*hello
中(贪婪量词),先将whellowwwhellowww
全部读取,进行匹配,没有找到。先去掉末尾一个字符“w”,“whellowwwhelloww”,还是没有找到。继续循环,去掉两个“w”之后,“whellowwwhello”找到这个字符串。开始索引为0,结束索引为14。
.*?hello
(勉强量词),先从字符串“whellowwwhellowww”读取一个字符“w”,匹配到了,继续往下读,读到“whello”的时候停止。开始索引为0,结束索引为6。
.*+hello
(具有量词),读取整个字符串,直接匹配,没有找到……(比较任性(⊙v⊙))。
捕获
上述操作也可用于一组字符上,这称为捕获组。一个捕获组是将一组字符作为一个单元处理。例如,(Kotlin)
是一个捕获组,这里的Kotlin是一个单元,ScalaScala
属于(Scala)*
正则表达式。在输入串中与捕获组匹配的部分将被保存,然后通过向后引用调用。
Java语言提供了对正则表达式中捕获组标识的计数,它是通过正则表达式中左括号的个数计数的。例如,在正则表达式((A)(B(C)))
中由四个捕获组:
((A)(B(C)))
(A)
(B(C))
(C)
可以调用Matcher类的groupCount方法来确定在一个模式中捕获组的数量。输入串中与捕获组的部分被保存在内存中,以备以后通过后引用(backreference
)再次调用。在正则表达式中,向后引用使用反斜线("")后跟一个表示回调的组号的数字。例如,表达式\d\d
定义了一个捕获组来匹配两个数字,它可以通过在后面加上“\1”回调。要匹配任何两位数字后跟相同的两位数字,正则表达式应该使用(\d\d)\1
。
正则表达式 | 输入串 | 结果 |
---|---|---|
([a-z][a-z])\1 | abab | 找到文本“abab”,开始索引为0,结束索引为4。 |
([a-z][a-z])\1 | abcd | 没有找到匹配。 |
([a-z][a-z]) | abcd | 找到文本“ab”,开始索引为0,结束索引为2。找到文本“cd”,开始索引为2,结束索引为4。 |
通用的正则表达式
用途 | 正则表达式 |
---|---|
电话号码:1开头,后面10位数字 | 1[0-9]\d{9}$ |
登录密码:6~16位数字和字符组合 | (?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$ |
登录密码:6位数字验证码 | \d{6}$ |
身份证号码验证 | ^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|[Xx])$ |