目录
概述
JAVA正则表达式从1.4版本开始支持,此前的JRE版本不支持正则表达式。
在JAVA中,正则表达式的使用主要通过java.util.regex
包下的Pattern
和Matcher
类来实现。
值得注意的是,使用的时候要注意转义问题,如果匹配元字符本身的字面值,通常需要在其前面加上反斜杠\
。例如要匹配一个实际的星号*
就需要写成\*
**Pattern:**一个Pattern
对象表示一个已编译的正则表达式。这个对象可以通过静态方法Pattern.compile(String regex)
从一个给定的正则表达式字符串创建。
**Matcher:**一个Matcher
对象是由Pattern
对象创建的,用于对输入字符串执行匹配操作。
一个简单的示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Test {
public static void main(String[] args) {
//定义正则表达式
//\b是匹配单词边界,多个纯大小写字母的字符,就是匹配单词,例如Hello
String regex = "\\b[A-Z][a-z]*\\b";
//编译正则表达式创建Pattern对象
Pattern pattern = Pattern.compile(regex);
//待匹配的字符串
String text = "Hello Regex";
//创建Matcher对象
Matcher matcher = pattern.matcher(text);
//查找并且打印所有匹配项
while (matcher.find()) {
System.out.println(matcher.group());
}
}
}
输出结果:
Hello
Regex
元字符
正则表达式的贪婪规则
正则表达式是有贪婪规则的,匹配一个或多个的情况下会尽可能多的匹配,可以通过后面加?
使这个正则表达式变懒惰,懒惰模式会尽可能少的匹配,并不是说原本匹配0或多次就匹配0次了,在符合条件的情况下会尽可能少的匹配。理解这个后面的表格就更容易理解了。
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Test2 {
public static void main(String[] args) {
//定义一个文本
String text = "ababccc";
//定义两个规则,一个贪婪模式,一个非贪婪模式
Pattern p1 = Pattern.compile("ab.*c");
Pattern p2 = Pattern.compile("ab.*?c");
//创建Matcher对象
Matcher m1 = p1.matcher(text);
Matcher m2 = p2.matcher(text);
//查找并打印结果
while (m1.find()) {
System.out.println(m1.group());
}
while (m2.find()) {
System.out.println(m2.group());
}
}
}
运行结果:
ababccc
ababc
基本元字符
元字符 | 说明 |
---|---|
. | 匹配除换行符以外的任意单个字符 |
^ | 匹配输入字符串的开始位置 |
$ | 匹配输入字符串的结束为止 |
* | 匹配前面的一个字符(子表达式)零次或多次 |
*? | *的懒惰模式 |
+ | 匹配前面的一个字符(子表达式)一次或多次 |
+? | +的懒惰模式 |
? | 匹配前面的一个字符(子表达式)零次或一次 |
?? | ?的懒惰模式 |
{n} | 匹配前面的一个字符(子表达式)恰好n次 |
{n,} | 匹配前面的一个字符(子表达式)至少n次 |
{n,}? | {n,}的懒惰模式 |
{n,m} | 匹配前面的一个字符(子表达式)至少n次但不超过m次 |
{n,m}? | {n,m}的懒惰模式 |
字符类
元字符 | 说明 | 举例 |
---|---|---|
[…] | 字符集合,匹配方括号内的任意一个字符 | 例如[abc] ,能够匹配的字符就可以是abc,edf等就不行 |
[^…] | 负向字符集合,匹配不在方括号内的任意一个字符 | 例如[^abc] ,能够匹配的字符就可以使edf等,abc就不行 |
[a-z] | 字符范围,匹配指定范围内的任意字符(范围里面的东西是按照ASCII码表来的,必须右边的ASCII值比左边的大) | 例如[A-Z] ,能够匹配的就是大写字母,剩下的都不行 |
[^A-Z] | 负向字符范围,匹配不在指定范围内的任意字符(和上面一样,也是按照ASCII表来的) | 例如[^A-Z] ,只要不是大写字母都能匹配 |
预定义字符类
元字符 | 说明 |
---|---|
\d | 匹配任何十进制的数字,等价于[0-9] |
\D | 匹配任何非数字字符,等价于[^0-9] |
\w | 匹配任何字母、数字或下划线,等价于[a-zA-Z_0-9] |
\W | 匹配任何非字母、数字或下划线的字符,等价于[^a-zA-Z_0-9] |
\s | 匹配任何空白字符,包括空格、制表符、换页符等,等价于[ \t\n\x0B\f\r] |
\S | 匹配任何非空白字符,等价于[^ \t\n\x0B\f\r] |
断言
元字符 | 说明 |
---|---|
\b | 单词边界,就是位于\w和\W之间 |
\B | 非单词边界,就是不在单词边界的位置 |
(?=…) | 正向肯定预查,确保紧接着当前位置之后的部分匹配给定的模式,但不消耗字符 |
(?!..) | 正向否定查询,确保紧接着当前位置之后的部分不匹配给定的模式 |
(?<=…) | 反向肯定预查,确保在当前位置之前的部分匹配给定的模式,但不消耗字符 |
(?<!..) | 反向否定预查,确保在当前位置之前的部分不匹配给定的模式 |
分组与捕获
元字符 | 说明 |
---|---|
(…) | 捕获括号内的子表达式,并允许后续引用 |
(?:…) | 非捕获括号,仅用于分组而不进行捕获 |
\n | 反向引用,其中n是一个数字,代表第n个捕获组的内容 |
(?…)或(?P…) | 命名捕获组 |
\k或(?P=name) | 引用前面命名的捕获组 |
分组与捕获的举例演示
(...)
示例:
String text = "Hello world, Hello word";
String regex = "(\\w+) (\\w+)";//注意,这中间有个空格
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
}
输出结果:
Hello
world
Hello
word
(?:...)
示例:
String text = "123-456-7890";
String regex = "(\\d{3})(?:-)(\\d{3})(?:-)(\\d{4})";//这里面的(?:-)代表匹配'-',但是仅仅是匹配,不会捕获
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
if (matcher.matches()) {
System.out.println(matcher.group(1));//输出结果:123
System.out.println(matcher.group(2));//输出结果:456
System.out.println(matcher.group(3));//输出结果:7890
}
\n
示例:
String text = "start-end-end-start";
String regex = "(\\w+)-(\\w+)-\\2-\\1";//这里面第一个(\\W+)就是第一个捕获组,第二个(\\w+)就是第二个捕获组,\\2是引用了第二个捕获组的内容,\\1是引用了第一个捕获组的内容
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
String result = matcher.replaceAll("$2-$1-$1-$2");
System.out.println(result); // 输出: end-start-start-end
(?<name>...)
或(?P<name>...)
示例:
String text = "John Doe, Jane Smith";
String regex = "(?<firstName>\\w+) (?<lastName>\\w+)";//这样定义就有组名了,而不是1,2了
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("First Name: " + matcher.group("firstName"));
System.out.println("Last Name: " + matcher.group("lastName"));
}
输出结果:
First Name: John
Last Name: Doe
First Name: Jane
Last Name: Smith
\k<name>
或(?P=name)
示例:
String text = "start-end-end-start";
String regex = "(?<first>\\w+)-(?<second>\\w+)-\\k<second>-\\k<first>";//和上面的\n类似,只不过这个可以定义成名字了
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
String result = matcher.replaceAll("${second}-${first}-${first}-${second}");
System.out.println(result); // 输出: end-start-start-end
转义字符
元字符 | 说明 |
---|---|
\ | 将下一个字符标记为特殊字符、原义字符或八进制转义符 |
\cX | 匹配控制字符 |
\xhh | 匹配以十六进制数hh 表示的字符 |
\uhhhh | 匹配Unicode字符,其中hhhh 是四位十六进制数 |
\Uxxxxxxxx | 匹配Unicode字符,其中hhhhhhhh 是八位十六进制数 |
模式修饰符
元字符 | 说明 |
---|---|
(?!) | 忽略大小写 |
(?m) | 多行模式,以^ 开始$ 结束 |
(?s) | 单行模式,使. 匹配所有字符,包括换行符 |
(?x) | 扩展模式,允许在正则表达式中添加注释和空白符提高可读性 |
(?-…) | 关闭特定模式选项 |
方法
Matcher提供的常用方法
方法名 | 说明 |
---|---|
boolean find() | 尝试查找下一个模式匹配的子序列,一般用于遍历 |
boolean matches() | 尝试将整个区域与模式匹配,一般用于输入整个字符串看是否完全符合规则 |
String group() | 返回前一个匹配操作的内容,一般用于获取当前匹配到的内容,和find()方法一起实现遍历 |
String group(int group) | 返回指定捕获组的前一个匹配操作的结果,一般用于获取特定的捕获组的内容 |
int start() | 返回最近一次匹配的起始索引,一般用于获取匹配的起始位置 |
int end() | 返回最近一次撇陪的结束索引,一般用于获取匹配的结束位置 |
String replaceAll(String replacement) | 替换输入字符串中所有与模式匹配的子序列,一般用于批量替换匹配的内容 |
String replaceFiest(String replacement) | 替换输入字符串中第一个与模式匹配的子序列,一般用于仅替换第一个匹配的内容 |
boolean lookingAt() | 尝试将输入序列的开头与模式相匹配,一般用于检查输入字符串开头是否符合正则表达式 |
void reset() | 重置匹配器,使其可以从头开始重新匹配,一般用于多次使用用一个匹配器时,重置其状态,例如遍历两次 |
Pattern提供的常用方法
方法名 | 说明 |
---|---|
static Pattern compile(String regex) | 将给定的正则表达式编译成一个模式,一般用于创建一个可以用于匹配操作的Pattern对象 |
static Pattern compile(String regex,int flags) | 将给定的正则表达式和标志编译成一个模式,一般用于创建一个带有特定标志的Pattern对象,以改变匹配行为 |
Matcher matcher(CharSequence input) | 创建一个新的Matcher对象,该对象可以用来匹配输入序列,一般用语言创建一个Matcher对象来进行具体的匹配操作 |
boolean matches(String regex,CharSequence input) | 尝试将输入序列与给定的正则表达式匹配,一般用于检查整个输入序列是否完全符合正则表达式 |
String[] split(CharSequence input) | 根据模式将输入序列拆分成子序列,一般用于将字符串按照正则表达式分成多个部分 |
String[] split(CharSequence input, int limit) | 根据模式将输入序列拆分成子序列,并指定最大拆分数,一般用于限制拆分结果的数量,例如只拆分前两个部分,拆到指定数量就不拆了,如果后面还有都是一个整体 |
String quote(String s) | 对字符串进行转义处理,使其可以作为正则表达式中的字面量使用,一般用于确保字符串中的特殊字符不会被解释为正则表达式的元字符 |
int flags() | 返回此模式的匹配标志,一般用于获取当前模式的匹配标志,以便了解其行为 |
String pattern() | 返回此模式的正则表达式字符串表示,获取编译后的模式对应的正则表达式字符串 |
正则表达式的实际应用
验证手机号是否合法
验证一个中国大陆的手机号是否合法(通常以1开头,长度为11位,并且第二位不能为1和2)
String phoneRegex = "^1[3-9]\\d{9}$";
Pattern pattern = Pattern.compile(phoneRegex);
解释:
^
表示字符串开始。1
匹配数字1。[3-9]
匹配3-9之间的任意一个数字。\\d{9}
匹配任何数字共9次。$
表示字符串结束。
验证邮箱是否合法
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
Pattern pattern = Pattern.compile(emailRegex);
^
表示字符串开始。[A-Za-z0-9+_.-]+
匹配字母、数字或特殊字符(+_.-)组成的一个或多个字符。@
匹配@符号。[A-Za-z0-9.-]+
匹配字母、数字或特殊字符(.-)组成的一个或多个字符,因为这里是域名部分,所以只允许包含点号和减号。$
表示字符串结束。
验证用户名是否合法(4-16位并且英文开头)
确保用户名以字母开始,且长度在4到16个字符之间
String usernameRegex = "^[A-Za-z][A-Za-z0-9_]{3,15}$";
Pattern pattern = Pattern.compile(usernameRegex);
^
表示字符串开始。[A-Za-z]
表示第一个字符必须是字母。[A-Za-z0-9_]{3,15}
表示接下来可以有3-15个字母、数字或下划线。$
表示字符串结束。
验证密码是否合法(6-16位的数字字母特殊符号组合)
String passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+])[A-Za-z\\d!@#$%^&*()_+]{6,16}$";
Pattern pattern = Pattern.compile(passwordRegex);
^
表示字符串开始。(?=.*[a-z])
表示至少有一个小写字母。(?=.*[A-Z])
表示至少有一个大写字母。(?=.*\\d)
表示至少有一个数字。(?=.*[!@#$%^&*()_+])
表示至少有一个特殊字符。[A-Za-z\\d!@#$%^&*()_+]{6,16}
表示总体上,密码应该由上述制定的字符串及组成,长度为6-16个字符。$
表示字符串结束。
拦截器和过滤器的路径是否放行
下面这个示例是我自己写的一个小项目的放行的判断:
//在这里定义一个方法,判断是否拦截
private boolean isAllowedRequest(HttpServletRequest request) {
String method = request.getMethod();//获取请求的方法(GET,POST,PUT等)
String path = request.getRequestURI();//获取请求的路径
String contextPath = request.getContextPath();//获取上文路径
if (contextPath != null && !contextPath.isEmpty()) {
path = path.substring(contextPath.length());//去除上下文路径
}
//定义允许的请求
if ("GET".equalsIgnoreCase(method)) {
return path.matches("^/tickets(/\\d+)?$") || path.matches("/comments");//注意看前面那句有一个?,可以存在也可以不存在
} else if ("POST".equalsIgnoreCase(method)) {
return path.equals("/users/login") || path.equals("/users/register") || path.equals("/admins/login");
}
return false;
}
这个示例里面根据传过来Http请求的方法,路径来判断的。
我们在这放行了GET的/tickets
路径请求,/tickets/1(这里是数字就行)
路径请求,/comments
路径请求;POST的/users/login
路径请求,/users/register
路径请求,/admins/login
路径请求。