正则表达式(Regex Expression)
字符串的合法验证自己编写验证逻辑使用正则表达式单字符匹配预定义字符量词(Quantifier)Pattern、MatcherMatcher 常用方法【Matcher 工具】:找出所有匹配的子序列Matcher – 贪婪、勉强、独占的区别捕获组(Capturing Group)捕获组 – 反向引用(Backreference)边界匹配符( Boundary Matcher)基本概念(终止符、输入、一行、单词边界)常用模式(CASE_INSENSITIVE、DOTALL、MULTILINE)常用的正则表达式String 类与正则表达式(replaceAll、repaceFirst、split)【练习】 替换字符串中的单词【练习】替换字符串的数字【练习】利用数字分隔字符串【练习】提取重叠的字母、数字Java笔记目录可以点这里:Java 强化笔记
字符串的合法验证
在开发中,经常会对一些字符串进行合法验证;
例如,对输入的邮件格式进行验证;
自己编写验证逻辑
我们可以写一段代码来对邮件字符串格式进行验证:
// 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");// truevalidate("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'); // falseregex.test('123456'); // falseregex.test('vv123_456') // trueregex.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开头, 但后面必须跟atString regex = "[^bcr]at"; "bat".matches(regex); // false"cat".matches(regex); // false"rat".matches(regex); // false"hat".matches(regex); // true
// foo后面只能跟1~5String 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个反斜杠\
开头的字符会被当做转义字符处理
因此,为了在正则表达式中完整地表示预定义字符,需要以2个反斜杠\\
开头,比如"\\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
量词(Quantifier)
// "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
Pattern、Matcher
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 = pile(regex);Matcher m = p.matcher(input);return m.matches();}
Matcher 常用方法
//如果整个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();
【Matcher 工具】:找出所有匹配的子序列
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 = pile(regx, flags); // 编译正则, 看是否合法, flags代表模式Matcherm = 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)
Matcher – 贪婪、勉强、独占的区别
这里再次放出这张表。
String input = "afooaaaaaafooa";findAll(".*foo", input); // 贪婪// "afooaaaaaafoo", [0, 13)findAll(".*?foo", input); // 勉强// "afoo", [0, 4)// "aaaaaafoo", [4, 13)findAll(".*+foo", input); // 独占// No match.
捕获组(Capturing Group)
简单的说,一对小括号里的内容就是一个捕获组;
String regex1 = "dog{3}";"doggg".matches(regex1); // trueString 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
捕获组 – 反向引用(Backreference)
反向引用: 可以使用反斜杠\
+ 组编号(从 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);
边界匹配符( Boundary Matcher)
基本概念(终止符、输入、一行、单词边界)
终止符(Final Terminator、Line Terminator)
\r
(回车符)、\n
(换行符)、\r\n
(回车换行符)
输入:整个字符串
一行:以终止符(或整个输入的结尾)结束的字符串片段
如果输入是dog\ndog\rdog
那么 3 个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、DOTALL、MULTILINE)
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为一行结尾, 中间为oString 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)
常用的正则表达式
正则表达式在线测试:/front-end/854
例如:
18 位身份证号码:\d{17}[\dXx]
中文字符:[\u4e00-\u9fa5]
String 类与正则表达式(replaceAll、repaceFirst、split)
String 类中接收正则表达式作为参数的常用方法有
public String replaceAll(String regex, string replacement)public replaceFirst(String regex, string replacement)public String[] split(String regex)
【练习】 替换字符串中的单词
将单词 row 换成单词 linereplace
是单纯的字符串替换(无法传入正则表达式),功能不如正则表达式强大。
比如这段代码,两者可以达到相同的功能。
// 将单词 row 换成单词 lineString 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 换成单词 lineString 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 = pile(regex);Matcher m = p.matcher(input);while (m.find()) {System.out.println(m.group() + "_" + m.group(1) + "、" + m.group(2));}
aa11_a、1dd44_d、4ff77_f、7
// 提取出"小写字母1小写字母2数字1数字2"格式的最后一个数字// 比如"ab12", 提取出2String input = "aa12+bb34-m56j*dd78/9900";String regex = "[a-z]{2}\\d(\\d)";Pattern p = pile(regex);Matcher m = p.matcher(input);while (m.find()) {System.out.println(m.group() + "_" + m.group(1));}
aa12_2bb34_4dd78_8
【Java 正则表达式】单字符匹配 预定字符 量词 Matcher(贪婪 勉强 独占模式) 捕获组 边界匹配符 String类与正则表达式