在开发中,经常会对一些字符串进行合法验证;
例如,对输入的邮件格式进行验证;
【图片待上传】
我们可以写一段代码来对邮件字符串格式进行验证:
// 6~18个字符,可使用字母、数字、下划线,需以字母开头
public static boolean validate(String email) {
if (email == null) {
System.out.println("不能为空");
return false;
}
char[] chars = email.toCharArray();
if (chars.length < 6 || chars.length > 18) {
System.out.println("必须是6~18个字符");
return false;
}
if(!isLetter(chars[0])) {
System.out.println("必须以字母开头");
return false;
}
for (int i = 1; i < chars.length; i++) {
char c = chars[i];
if (isLetter(c) || isDigit(c) || c == '_') continue;
System.out.println("必须由字母、数字、下划线组成");
return false;
}
return true;
}
// 判断是否是字母
public static boolean isLetter(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
// 判断是否是数字
public static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
public static void main(String[] args) {
// 必须是6~18个字符
validate("12345");
// 必须以字母开头
validate("123456");
// true
validate("vv123_456");
// 必须由字母、数字、下划线组成
validate("vv123+/?456");
}
上面验证逻辑的代码比较长,如果用正则表达式则可以轻松搞定:(正则是什么请继续看下去)
// String regex = "[a-zA-Z][a-zA-Z0-9_]{5,17}"; // 两种写法等价
String regex = "[a-zA-Z]\\w{5,17}";
"12345".matches(regex); // false
"123456".matches(regex); // false
"vv123_456".matches(regex); // true
"vv123+/?456".matches(regex); // false
[a-zA-Z]\\w{5,17}
是一个正则表达式
正则表达式是一种通用的技术,适用于绝大多数流行编程语言
// JavaScript 中的正则表达式
const regex = /[a-zA-Z]\w{5, 17}/;
regex.test('12345'); // false
regex.test('123456'); // false
regex.test('vv123_456') // true
regex.test('vv123+/?456') // false
// 只能以b、c、r开头, 后面必须跟着at
// 等价于 [b|c|r]at、(b|c|r)at, 圆括号不可能省略 "|"
String regex = "[bcr]at";
"bat".matches(regex); // true
"cat".matches(regex); // true
"rat".matches(regex); // true
"hat".matches(regex); // false
// 不能以b、c、r开头, 但后面必须跟at
String regex = "[^bcr]at";
"bat".matches(regex); // false
"cat".matches(regex); // false
"rat".matches(regex); // false
"hat".matches(regex); // true
// foo后面只能跟1~5
String regex = "foo[1-5]";
"foo3".matches(regex); // true
"foo6".matches(regex); // false
// foo后面只能跟1-5以外的事情
String regex = "foo[^1-5]";
"foo3".matches(regex); // false
"foo6".matches(regex); // true
// "foo1-5"必须全字匹配
String regex = "foo1-5";
"foo1-5".matches(regex); // true
"fool".matches(regex); // false
"foo5".matches(regex); // flase
// 等价于 "[0-46-8]"
String regex = "[0-4[6-8]]";
"5".matches(regex); // false
"7".matches(regex); // true
"9".matches(regex); // false
// [0-9] 与 [^345] 的交集, 即 [0-2[6-9]]
String regex = "[0-9&&[^345]]";
"2".matches(regex); // true
"3".matches(regex); // false
"4".matches(regex); // false
"5".matches(regex); // false
"6".matches(regex); // true
// [0-9] 与 [345] 的交集, 等价于 [3-5]
String regex = "[0-9&&[345]]";
"2".matches(regex); // false
"3".matches(regex); // true
"4".matches(regex); // true
"5".matches(regex); // trye
"6".matches(regex); // false
Java 中,以 1个反斜杠\
开头的字符会被当做转义字符处理
\\
开头,比如 "\\d"
// 匹配任意单个字符
String regex = ".";
"@".matches(regex);
"c".matches(regex);
"6".matches(regex);
".".matches(regex);
// 只匹配 "."
String regex = "\\.";
"@".matches(regex); // false
"c".matches(regex); // false
"6".matches(regex); // fasle
".".matches(regex); // true
// 全字匹配 "[123]"
String regex = "\\[123\\]";
"1".matches(regex); // false
"2".matches(regex); // false
"3".matches(regex); // false
"[123]".matches(regex); // true
// 匹配数字, 等价于[0-9]
String regex = "\\d";
"c".matches(regex); // false
"6".matches(regex); // true
// 匹配非数字, 等价于[^0-9]
String regex = "\\D";
"c".matches(regex); // true
"6".matches(regex); // false
// "666"完全匹配
String regex = "6{3}";
"66".matches(regex); // false
"666".matches(regex); // true
"6666".matches(regex); // false
// "6"出现2到4次, "66"、"666"、"6666"
String regex = "6{2,4}";
"6".matches(regex); // false
"66".matches(regex); // true
"666".matches(regex); // true
"6666".matches(regex); // true
"66666".matches(regex); // false
// "6"出现2次以上
String regex = "6{2,}";
"6".matches(regex); // false
"66".matches(regex); // true
"666".matches(regex); // true
"6666".matches(regex); // true
"66666".matches(regex); // true
// "6"出现0次或者1次
String regex = "6?";
"".matches(regex); // true
"6".matches(regex); // true
"66".matches(regex); // false
// "6"出现任意次数
String regex = "6*";
"".matches(regex); // true
"6".matches(regex); // true
"66".matches(regex); // true
"67".matches(regex); // false, 必须完全匹配
// "6"出现至少1次
String regex = "6+";
"".matches(regex); // false
"6".matches(regex); // true
"66".matches(regex); // true
String
的 matches
方法底层用到了 Pattern
、Matcher
两个类;
// java.lang.String 源码:
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}
// java.util.regex.Pattern 源码:
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
//如果整个input与regex匹配,就返回 true
public boolean matches();
//如果从input中找到了与regex匹配的子序列,就返回 true
//如果匹配成功,可以通过start、end、group方法获取更多信息
//每次的查找范围会先剔除此前已经查找过的范围
public boolean find();
//返回上一次匹配成功的开始索引
public int start();
//返回上一次匹配成功的结束索引
public int end();
//返回上一次匹配成功的input子序列
public String group();
public static void findAll(String regex, String input) {
findAll(regex, input, 0);
}
public static void findAll(String regx, String input, int flags) {
if (regx == null || input == null) return;
Pattern p = Pattern.compile(regx, flags); // 编译正则, 看是否合法, flags代表模式
Matcher m = p.matcher(input); // 匹配, 返回一个匹配器
boolean found = false;
while (m.find()) {
found = true;
System.out.format("\"%s\", [%d, %d)%n", m.group(), m.start(), m.end());
}
if (!found) {
System.out.println("No match.");
}
}
Matcher
- 示例:
findAll("\\d{3}", "111_222_333_444_555");
/*
"111", [0, 3)
"222", [4, 7)
"333", [8, 11)
"444", [12, 15)
"555", [16, 19)
*/
String regex = "123";
findAll(regex, "123");
// "123", [0, 3)
findAll(regex, "6_123_123_123_7");
/*
"123", [2, 5)
"123", [6, 9)
"123", [10, 13)
*/
String regex = "[abc]{3}";
findAll(regex, "abccabaaaccbbbc");
/*
"abc", [0, 3)
"cab", [3, 6)
"aaa", [6, 9)
"ccb", [9, 12)
"bbc", [12, 15)
*/
String regex = "\\d{2}";
findAll(regex, "0_12_345_67_8");
/*
"12", [2, 4)
"34", [5, 7)
"67", [9, 11)
*/
String input = "";
findAll("a?", input);
// "", [0, 0)
String input = "";
findAll("a?", input);
// "", [0, 0)
findAll("a*", input);
// "", [0, 0)
findAll("a+", input);
// No match.
String input = "a";
findAll("a?", input);
// "a", [0, 1)
// "", [1, 1)
findAll("a*", input);
// "a", [0, 1)
// "", [1, 1)
findAll("a+", input);
// "a", [0, 1)
String input = "abbaaa";
findAll("a?", input);
/*
"a", [0, 1)
"", [1, 1)
"", [2, 2)
"a", [3, 4)
"a", [4, 5)
"a", [5, 6)
"", [6, 6)
*/
findAll("a*", input);
/*
"a", [0, 1)
"", [1, 1)
"", [2, 2)
"aaa", [3, 6)
"", [6, 6)
*/
findAll("a+", input);
// "a", [0, 1)
// "aaa", [3, 6)
这里再次放出这张表
String input = "afooaaaaaafooa";
findAll(".*foo", input); // 贪婪
// "afooaaaaaafoo", [0, 13)
findAll(".*?foo", input); // 勉强
// "afoo", [0, 4)
// "aaaaaafoo", [4, 13)
findAll(".*+foo", input); // 独占
// No match.
String input = "afooaaaaaafooa";
findAll(".*foo", input); // 贪婪
// "afooaaaaaafoo", [0, 13)
findAll(".*?foo", input); // 勉强
// "afoo", [0, 4)
// "aaaaaafoo", [4, 13)
findAll(".*+foo", input); // 独占
// No match.
简单的说,一对小括号里的内容就是一个捕获组;
String regex1 = "dog{3}";
"doggg".matches(regex1); // true
String regex2 = "[dog]{3}";
"ddd".matches(regex2); // true
"ooo".matches(regex2); // true
"ggg".matches(regex2); // true
"dog".matches(regex2); // true
"gog".matches(regex2); // true
"gdo".matches(regex2); // true
// ... 共 3 * 3 * 3 = 27 种可能
// (dog)就是一个捕获组
String regex3 = "(dog){3}";
"dogdogdog".matches(regex3); // true
反向引用: 可以使用反斜杠\
+ 组编号(从 1 开始)来引用组的内容
// (\\d\\d)是一个捕获组, \\1表示引用第一个捕获组(内容要相同)
String regex = "(\\d\\d)\\1";
"1212".matches(regex); // true
"1234".matches(regex); // false
// 总共有2个组
// 编号1: ([a-z]{2})
// 编号2: ([A-Z]{2})
// \\2\\1 表示先引用第2组再引用第1组
String regex = "([a-z]{2})([A-Z]{2})\\2\\1";
"mjPKPKmj".matches(regex); // true
"mjPKmjPK".matches(regex); // false
// 总共有4个组
// 编号1: ((I)( Love( You)))
// 编号2: (I)
// 编号3: ( Love( You))
// 编号4: ( You)
// \\3{2} 表示引用2次第3组, 即后面跟 "Love You Love You"
String regex = "((I)( Love( You)))\\3{2}";
"I Love You Love You Love You".matches(regex); // true
String input = "aaabbbb";
// 下面的正则等价于: "([a-z])\\1{3}"
String regex = "([a-z])\\1\\1\\1";
findAll(regex, input);
终止符(Final Terminator、Line Terminator)
\r
(回车符)、\n
(换行符)、\r\n
(回车换行符)输入:整个字符串
一行:以终止符(或整个输入的结尾)结束的字符串片段
dog\ndog\rdog
dog
都是一行单词边界:
// 哪些东西是单词边界?
// 除开英文字母大小写、阿拉伯数字、下划线、其他国家的正常文字以外的字符
String input = "dog_dog6dog+dog-dog哈";
findAll("\\bdog\\b", input);
// "dog", [12, 15)
\b
代表单词边界:
// \\b是单词边界, 要求dog左边和右边都是单词边界
String regex = "\\bdog\\b";
// " " 和 "." 是单词边界
findAll(regex, "This is a dog.");
// "dog", [10, 13)
findAll(regex, "This is a doggie.");
// No match.
// 开头视作单词边界
findAll(regex, "dog is cute");
// "dog", [0, 3)
// ","是单词边界
findAll(regex, "I love cat,dog,pig.");
// "dog", [11, 14)
\B
代表非单词边界:
// dog左边是单词边界, dog右边不是单词边界
String regex = "\\bdog\\B";
findAll(regex, "This is a dog.");
// No match.
findAll(regex, "This is a doggie.");
// "dog", [10, 13)
findAll(regex, "dog is cute");
// No match.
findAll(regex, "I love cat,dog,pig.");
// No match.
^
代表一行的开头,$
代表一行的结尾:
// ^是一行的开头, $是一行的结尾
// 要求dog, 且d是行开头, g是行结尾
String regex = "^dog$";
findAll(regex, "dog");
// "dog", [0, 3)
findAll(regex, " dog");
// No match.
// -------------------------------------
findAll("\\s*dog$", " dog");
// " dog", [0, 7)
findAll("^dog\\w*", "dogblahblah");
// "dogblahblah", [0, 11)
\A
代表 输入的开头、\z
代表输入的结尾、\Z
代表输入的结尾(结尾可以有终结符):
// "\A" 代表输入的开头
// "\z" 代表输入的结尾
// "\Z" 代表输入的结尾(结尾可以有终结符)
String regex1 = "\\Adog\\z";
String regex2 = "\\Adog\\Z";
findAll(regex1, "dog");
// "dog", [0, 3)
findAll(regex2, "dog");
findAll(regex1, "dog\n");
// No match.
findAll(regex2, "dog\n");
// "dog", [0, 3)
findAll(regex1, "dog\ndog\rdog");
// No match.
findAll(regex2, "dog\ndog\rdog");
// No match.
findAll(regex1, "dog\ndog\rdog", Pattern.MULTILINE);
// No match.
findAll(regex2, "dog\ndog\rdog", Pattern.MULTILINE);
// No match.
\G
代表上一次匹配的结尾(很少用到)
// 开头看做一次匹配的结尾
String regex = "\\Gdog";
findAll(regex, "dog");
// "dog", [0, 3)
findAll(regex, "dog dog");
// "dog", [0, 3)
findAll(regex, "dogdog");
// "dog", [0, 3)
// "dog", [3, 6)
CASE_INSENSITIVE
忽略大小写模式:
String regex = "dog";
String input = "Dog_dog_DOG";
// 默认是
findAll(regex, input);
// "dog", [4, 7)
// 设置忽略大小写模式
findAll(regex, input, Pattern.CASE_INSENSITIVE);
// "Dog", [0, 3)
// "dog", [4, 7)
// "DOG", [8, 11)
// 忽略大小写模式, 正则写法
findAll("(?i)dog", input);
// "Dog", [0, 3)
// "dog", [4, 7)
// "DOG", [8, 11)
DOTALL
单行模式:
// "."代表匹配任意字符
String regex = ".";
String input = "\r\n";
findAll(regex, input); // 默认无法匹配到终结符
// No match.
// 单行模式(可以匹配任意字符, 包括终止符)
findAll(regex, input, Pattern.DOTALL);
// "\r", [0, 1)
// "\n", [1, 2)
// 多行模式(^、$ 能真正匹配一行的开头和结尾, 无法匹配到终结符)
findAll(regex, input, Pattern.MULTILINE);
// No match.
findAll(regex, input, Pattern.MULTILINE | Pattern.DOTALL);
// "\r", [0, 1)
// "\n", [1, 2)
MULTILINE
多行模式:
// 以d为一行开头, g为一行结尾, 中间为o
String regex = "^dog$";
String input = "dog\ndog\rdog";
findAll(regex, input);
// No match.
findAll(regex, input, Pattern.DOTALL); // 单行模式, 可以匹配到终结符, 无法匹配到 ^ 与 $
// No match.
findAll(regex, input, Pattern.MULTILINE); // 多行模式(^、$ 才能真正匹配一行的开头和结尾)
// "dog", [0, 3)
// "dog", [4, 7)
// "dog", [8, 11)
// 单行模式 与 多行模式 的内容都能匹配到
findAll(regex, input, Pattern.DOTALL | Pattern.MULTILINE);
// "dog", [0, 3)
// "dog", [4, 7)
// "dog", [8, 11)
正则表达式在线测试:https://c.runoob.com/front-end/854
例如:
18 位身份证号码:\d{17}[\dXx]
中文字符:[\u4e00-\u9fa5]
String 类中接收正则表达式作为参数的常用方法有
public String replaceAll(String regex, string replacement)
public replaceFirst(String regex, string replacement)
public String[] split(String regex)
replace
是单纯的字符串替换(无法传入正则表达式),功能不如正则表达式强大。
比如这段代码,两者可以达到相同的功能。
// 将单词 row 换成单词 line
String s1 = "The row we are looking for is row 8.";
String s2 = s1.replace("row", "line"); // 成功替换
// The line we are looking for is line 8.
String s3 = s1.replaceAll("\\brow\\b", "line"); // 成功替换
// The line we are looking for is line 8.
replaceAll
可以传入正则表达式,功能更加强大。
这段代码,单纯的字符串替换无法达到效果,需要使用正则表达式。
// 将单词 row 换成单词 line
String s1 = "Tomorrow I will wear in brown standing in row 10.";
String s2 = s1.replace("row", "line"); // 替换错误, 没有达到要求
// Tomorline I will wear in blinen standing in line 10.
String s3 = s1.replaceAll("\\brow\\b", "line"); // 成功替换
// Tomorrow I will wear in brown standing in line 10.
**
// 将所有连续的数字替换为 "**"
String s1 = "ab12c3d456efg7h89i1011jk12lmn";
String s2 = s1.replaceAll("\\d+", "**");
// ab**c**d**efg**h**i**jk**lmn
String s1 = "ab12c3d456efg7h89i1011jk12lmn";
String[] strs = s1.split("\\d+");
// [ab, c, d, efg, h, i, jk, lmn]
提取出"小写字母1小写字母2数字1数字2"格式的字母、数字
"aa33"
, 提取出 a
、3
;"aa33"
, 提取出 a
、3
"aa12"
、"ab44"
、"aabb"
、"5566"
, 不符合条件// 提取出"小写字母1小写字母2数字1数字2"格式的字母、数字
// 比如"aa33", 提取出a、3
// 比如"aa12"、"ab44"、"aabb"、"5566", 不符合条件
String input = "aa11+bb23-mj33*dd44/5566%ff77";
String regex = "([a-z])\\1(\\d)\\2";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
while (m.find()) {
System.out.println(m.group() + "_" + m.group(1) + "、" + m.group(2));
}
aa11_a、1
dd44_d、4
ff77_f、7
// 提取出"小写字母1小写字母2数字1数字2"格式的最后一个数字
// 比如"ab12", 提取出2
String input = "aa12+bb34-m56j*dd78/9900";
String regex = "[a-z]{2}\\d(\\d)";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
while (m.find()) {
System.out.println(m.group() + "_" + m.group(1));
}
aa12_2
bb34_4
dd78_8