【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字

正则表达式

文章目录

  • 正则表达式
    • 需求
      • 字符串的合法验证
      • 自己编写验证逻辑
      • 使用正则表达式
    • 单字符匹配
    • 预定义字符
    • 量词
    • Pattern、Matcher
    • Matcher
      • Matcher 常用方法
      • 找出所有匹配的子序列
      • Matcher – 贪婪、勉强、独占的区别
    • 贪婪、勉强、独占
    • 捕获组
      • 捕获组 – 反向引用(Backreference)
    • 边界匹配符
      • 基本概念(终止符、输入、一行、单词边界)
    • 常用模式(CASE_INSENSITIVE、DOTALL、MULTILINE)
    • 常用的正则表达式
    • String 类与正则表达式(replaceAll、repaceFirst、split)
    • 练习
      • 【练习】 替换字符串中的单词
      • 【练习】替换字符串的数字
      • 【练习】利用数字分隔字符串
      • 【练习】提取重叠的字母、数字

需求

字符串的合法验证

在开发中,经常会对一些字符串进行合法验证

例如,对输入的邮件格式进行验证;

【图片待上传】

自己编写验证逻辑

我们可以写一段代码来对邮件字符串格式进行验证:

// 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

单字符匹配

【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第1张图片

// 只能以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正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第2张图片

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

量词

【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第3张图片

// "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

Stringmatches 方法底层用到了 PatternMatcher 两个类;

// 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();
}

Matcher

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();

找出所有匹配的子序列

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)

Matcher – 贪婪、勉强、独占的区别

这里再次放出这张表

【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第4张图片

String input = "afooaaaaaafooa";
findAll(".*foo", input); // 贪婪
// "afooaaaaaafoo", [0, 13)

findAll(".*?foo", input); // 勉强
// "afoo", [0, 4)
// "aaaaaafoo", [4, 13)

findAll(".*+foo", input); // 独占
// No match.

贪婪、勉强、独占

  • 贪婪
    • 先吞掉整个input进行匹配
      • 若匹配失败,则吐出最后一个字符
    • 然后再次尝试匹配,重复此过程,直到匹配成功

  • 勉强
    • 先吞掉input的第一个字符进行匹配
      • 若匹配失败,则再吞掉下一个字符
    • 然后再次尝试匹配,重复此过程,直到匹配成功

  • 独占
    • 吞掉整个input进行唯一的次匹配
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

捕获组 – 反向引用(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);

边界匹配符

【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第5张图片

基本概念(终止符、输入、一行、单词边界)

终止符(Final Terminator、Line Terminator)

  • \r(回车符)、\n(换行符)、\r\n(回车换行符)

输入:整个字符串

一行:以终止符(或整个输入的结尾)结束的字符串片段

  • 如果输入是 dog\ndog\rdog
  • 那么 3 个 dog 都是一行
    匹配模式要设置为多行模式,终止符才会生效,否则还是看作单行)

单词边界

【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第6张图片

// 哪些东西是单词边界?
// 除开英文字母大小写、阿拉伯数字、下划线、其他国家的正常文字以外的字符
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)

【Java正则表达式】字符串的合法验证、单字符匹配、预定义字符、量词、贪婪、勉强、独占、捕获组、反向引用、边界匹配符、常用模式、替换字符串中的单词和数字、数字分割字符串、提取重叠的字母和数字_第7张图片

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 类与正则表达式(replaceAll、repaceFirst、split)

String 类中接收正则表达式作为参数的常用方法有

public String replaceAll(String regex, string replacement)

public replaceFirst(String regex, string replacement)

public String[] split(String regex)

练习

【练习】 替换字符串中的单词

  • 将单词 row 换成单词 line

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", 提取出 a3
  • 比如 "aa33", 提取出 a3
  • 比如 "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

你可能感兴趣的:(Java基础,java,开发语言,后端)