文章目录
简介
什么是正则表达式
正则表达式(Regular Expression,通常简称为regex,regexp或RE),是一种强大的文本处理工具,它是一种用于匹配字符串中字符组合的模式。正则表达式可以用来检查一个字符串是否含有某种子串、将匹配的子串替换或从某个字符串中取出符合某个条件的子串等。
例如,正则表达式 a.b
可以匹配 “acb”、“aeb”、“a1b” 等,.
是一个特殊字符,代表任何字符(除了换行符)。
String regex = "a.b";
String str = "acb";
boolean matches = str.matches(regex); // returns true
正则表达式在Java中的应用
在Java中,正则表达式主要通过 java.util.regex
包中的两个类 Pattern
和 Matcher
来使用。Pattern
类用于编译正则表达式,而 Matcher
类用于执行通过 Pattern
类编译的正则表达式在字符序列上的操作。
正则表达式在Java中的应用广泛,包括但不限于:
- 验证输入:例如,检查用户输入的电子邮件地址或电话号码是否有效。
- 搜索文本:例如,搜索日志文件中的特定错误模式。
- 文本替换:例如,将文本文件中的所有 “colour” 替换为 “color”。
- 数据提取:例如,从文本文件中提取所有电子邮件地址。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
Pattern pattern = Pattern.compile("a.b");
Matcher matcher = pattern.matcher("acb");
boolean matches = matcher.matches(); // returns true
Java正则表达式基础
常用的正则表达式符号和规则
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文本模式。以下是一些常用的元字符和它们的含义:
.
:匹配任何单个字符(除了换行符)。*
:匹配前面的元素零次或多次。+
:匹配前面的元素一次或多次。?
:匹配前面的元素零次或一次。^
:匹配输入字符串的开始位置。$
:匹配输入字符串的结束位置。{n}
:精确匹配n次前面的字符或者表达式。{n,}
:匹配n次或更多次前面的字符或者表达式。{n,m}
:匹配至少n次,至多m次前面的字符或者表达式。[abc]
:匹配方括号内的任何字符,例如a
、b
或c
。[^abc]
:匹配除了方括号内字符的任何字符。(pattern)
:匹配pattern并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到。\d
:匹配数字,等价于[0-9]
。\D
:匹配非数字,等价于[^0-9]
。\s
:匹配任何空白字符,包括空格、制表符、换页符等等,等价于[ \f\n\r\t\v]
。\S
:匹配任何非空白字符,等价于[^ \f\n\r\t\v]
。\w
:匹配字母或数字或下划线或汉字,等价于[A-Za-z0-9_]
。\W
:匹配任何非单词字符,等价于[^A-Za-z0-9_]
。
Java中的正则表达式类:Pattern和Matcher
在Java中,正则表达式的操作主要通过 java.util.regex
包中的两个类 Pattern
和 Matcher
来实现。
-
Pattern
类:这是一个正则表达式的编译表示。Pattern 对象是一个多态的表达式,给定的正则表达式必须首先被编译为此类的一个实例。然后,这个模式可以被用来创建 Matcher 对象,以便匹配任意字符序列。 -
Matcher
类:通过解释 Pattern 对象而得到的正则表达式匹配的引擎。所有的匹配操作都通过这个类完成。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
// 创建 Pattern 对象
Pattern r = Pattern.compile("[a-z]+");
// 现在创建 matcher 对象
Matcher m = r.matcher("hello world");
boolean b = m.matches();
Java正则表达式实例
字符串匹配
在Java中,我们可以使用 String
类的 matches()
方法来测试字符串是否匹配给定的正则表达式。
String content = "Hello, this is a test.";
// 返回 true
boolean isMatch = content.matches(".*test.*");
在上述代码中,.*test.*
是一个正则表达式,它表示任何包含 “test” 的字符串。
字符串搜索
我们可以使用 Matcher
类的 find()
方法来搜索字符串中的子串。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
String content = "I am a student. I am learning Java.";
Pattern pattern = Pattern.compile("am");
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("Found at: " + matcher.start() + " - " + matcher.end());
// Found at: 2 - 4
// Found at: 18 - 20
}
在上述代码中,我们创建了一个 Pattern
对象,用于编译正则表达式 “am”,然后使用 Matcher
对象来查找与该模式匹配的子串。
字符串替换
我们可以使用 String
类的 replaceAll()
方法来替换字符串中匹配给定正则表达式的子串。
String content = "I am a student. I am learning Java.";
String updatedContent = content.replaceAll("am", "AM");
String updatedBlankSpace = content.replaceAll("\\s+", "_")
// 输出 "I AM a student. I AM learning Java."
System.out.println(updatedContent);
// 输出 "I_am_a_student._I_am_learning_Java."
System.out.println(updatedBlankSpace);
字符串分割
我们可以使用 String
类的 split()
方法来根据给定的正则表达式分割字符串。
String content = "I am a student. I am learning Java.";
String[] words = content.split("\\s+"); // 使用一个或多个空格进行分割
for (String word : words) {
System.out.println(word);
}
验证电子邮件地址
^
:表示字符串的开始。[\\w-]+
:匹配一个或多个字母、数字、下划线或连字符。这部分匹配电子邮件地址的本地部分(即@
符号之前的部分)。(\\.[\\w-]+)*
:匹配一个点号后跟一个或多个字母、数字、下划线或连字符的序列,出现0次或多次。这部分允许本地部分包含点号分隔的子部分。@
:匹配一个@
符号。[\\w-]+
:匹配一个或多个字母、数字、下划线或连字符。这部分匹配域名的第一部分。(\\.[\\w-]+)*
:匹配一个点号后跟一个或多个字母、数字、下划线或连字符的序列,出现0次或多次。这部分允许域名包含点号分隔的子域。(\\.[a-zA-Z]{2,})
:匹配一个点号后跟两个或更多字母。这部分匹配顶级域名(如.com
、.org
等)。$
:表示字符串的结束。
所以,这个正则表达式的含义是:一个字符串从开始到结束必须是一个有效的电子邮件地址,格式为 local-part@domain
,其中 local-part
和 domain
可以包含字母、数字、下划线、连字符和点号,顶级域名部分至少包含两个字母。
需要注意的是,这个正则表达式虽然涵盖了大多数常见的电子邮件格式,但并不完全符合所有可能的电子邮件格式。例如,它不支持带有引号的本地部分或包含特殊字符的域名。如果你需要一个更精确的电子邮件正则表达式,可能需要使用更复杂的模式。
String email = "user@example.com";
Pattern pattern = Pattern.compile("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)*(\\.[a-zA-Z]{2,})$");
Matcher matcher = pattern.matcher(email);
System.out.println(matcher.matches()); // 输出:true 或 false
验证手机号码(假设我们只接受10位数字)
String phoneNumber = "1234567890";
Pattern pattern = Pattern.compile("^\\d{10}$");
Matcher matcher = pattern.matcher(phoneNumber);
System.out.println(matcher.matches()); // 输出:true 或 false
验证URL
^
:表示字符串的开始。(http|https|ftp)://
:匹配URL的协议部分,可以是http、https或ftp,后面跟着://
。[^\\s/$.?#].
:匹配URL的主机名部分。具体来说:[^\\s/$.?#]
:匹配除空白字符、/
、$
、.
、?
、#
之外的任何字符。.
:匹配任何字符,除了换行符。
[^\\s]*
:匹配除空白字符之外的任何字符,出现0次或多次。$
:表示字符串的结束。
所以,这个正则表达式的含义是:一个字符串从开始到结束必须是一个有效的URL,格式为 protocol://hostname/path
,其中 protocol
可以是http、https或ftp,hostname
和 path
不能包含空白字符、/
、$
、.
、?
、#
。
需要注意的是,这个正则表达式并不完全符合URL的规范,例如它不支持包含/
、$
、.
、?
、#
的路径,也不支持包含.
的主机名。如果你需要一个更精确的URL正则表达式,可能需要使用更复杂的模式。
String url = "http://www.example.com";
Pattern pattern = Pattern.compile("^(http|https|ftp)://[^\\s/$.?#].[^\\s]*$");
Matcher matcher = pattern.matcher(url);
System.out.println(matcher.matches()); // 输出:true 或 false
验证IP地址
^
:表示字符串的开始。((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}
:匹配前三段数字和它们后面的点号。25[0-5]
:匹配250到255之间的数字。2[0-4]\\d
:匹配200到249之间的数字。[01]?\\d\\d?
:匹配0到199之间的数字。具体来说:[01]?
:匹配0或1,出现0次或1次。\\d\\d?
:匹配一个或两个数字。
\\.
:匹配一个点号。{3}
:表示前面的模式(即((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.)
)重复3次。
(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)
:匹配最后一段数字。- 这部分与前面的模式相同,但不包含点号。
$
:表示字符串的结束。
所以,这个正则表达式的含义是:一个字符串从开始到结束必须是一个有效的IPv4地址,格式为 xxx.xxx.xxx.xxx
,其中每个 xxx
是0到255之间的数字。
String ip = "192.168.1.1";
Pattern pattern = Pattern.compile("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$");
Matcher matcher = pattern.matcher(ip);
System.out.println(matcher.matches()); // 输出:true 或 false
提取字符串中的数字
String str = "abc123def456";
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
// 输出 123 456
}
验证是否是数字
^
:表示字符串的开始。\\d
:匹配一个数字字符。\\d
是一个转义序列,表示任何一个数字(0-9)。+
:表示前面的模式(即\\d
)出现一次或多次。如果没有这个符号,正则表达式就只会匹配一个数字字符。$
:表示字符串的结束。
所以,这个正则表达式的含义是:一个字符串从开始到结束只包含一个或多个数字字符。
String number = "12345";
Pattern pattern = Pattern.compile("^\\d+$");
Matcher matcher = pattern.matcher(number);
System.out.println(matcher.matches()); // 输出:true 或 false
验证是否是字母
^
:表示字符串的开始。[a-zA-Z]
:这是一个字符集,匹配任何一个英文字母,无论大小写。a-z
表示所有小写字母,A-Z
表示所有大写字母。在字符集中,-
是一个范围操作符,表示范围从前面的字符到后面的字符。+
:表示前面的模式(即[a-zA-Z]
)出现一次或多次。如果没有这个符号,正则表达式就只会匹配一个字符。$
:表示字符串的结束。
所以,这个正则表达式的含义是:一个字符串从开始到结束只包含一个或多个英文字母(无论大小写)。
String letters = "abcABC";
Pattern pattern = Pattern.compile("^[a-zA-Z]+$");
Matcher matcher = pattern.matcher(letters);
System.out.println(matcher.matches()); // 输出:true 或 false
验证是否是日期(格式:yyyy-mm-dd)
这个正则表达式的含义如下:
^
:表示字符串的开始。\\d{4}
:匹配四个数字。\\d
是一个表示数字字符的转义序列,{4}
表示前面的模式(即数字字符)要出现4次。-
:匹配一个连字符(减号)。\\d{2}
:匹配两个数字。\\d
是一个表示数字字符的转义序列,{2}
表示前面的模式(即数字字符)要出现2次。-
:匹配一个连字符(减号)。\\d{2}
:匹配两个数字。\\d
是一个表示数字字符的转义序列,{2}
表示前面的模式(即数字字符)要出现2次。$
:表示字符串的结束。
String date = "2024-06-02";
Pattern pattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$");
Matcher matcher = pattern.matcher(date);
System.out.println(matcher.matches()); // 输出:true 或 false
验证密码强度(至少8个字符,包含大写字母,小写字母和数字)
这个正则表达式的含义如下:
^
表示字符串的开始。(?=.*[0-9])
确保字符串中至少包含一个数字。(?=.*[a-z])
确保字符串中至少包含一个小写字母。(?=.*[A-Z])
确保字符串中至少包含一个大写字母。.{8,}$
确保字符串的长度至少为8个字符,.
表示任意字符,{8,}
表示至少出现8次,$
表示字符串的结尾。
String password = "Password123";
Pattern pattern = Pattern.compile("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}$");
Matcher matcher = pattern.matcher(password);
System.out.println(matcher.matches()); // 输出:true 或 false
Java正则表达式的实现
Java正则表达式主要由 Pattern
类和 Matcher
类实现。下面我们来详细了解一下这两个类的实现。
Pattern类的实现
Pattern
类是Java正则表达式的编译表示,它的实例是不可变的,可以安全地被多个并发线程使用。Pattern
类没有公开的构造函数,要创建一个 Pattern
对象,你必须首先调用其公开的静态 compile
方法,它返回一个 Pattern
对象。这个方法需要一个正则表达式作为它的第一个参数。
Pattern pattern = Pattern.compile("[a-z]+");
在上述代码中,我们创建了一个 Pattern
对象,它表示一个或多个小写字母的序列。
Pattern
类还提供了一些其他的方法,例如:
matcher(CharSequence input)
:生成一个Matcher
对象,用于匹配给定的字符序列。split(CharSequence input)
:根据此模式分割给定的输入序列。pattern()
:返回此模式的字符串表示形式。
Matcher类的实现
Matcher
类是通过解释 Pattern
对象而得到的正则表达式匹配的引擎。所有的匹配操作都通过这个类完成。
你不能直接创建一个 Matcher
对象,而是需要通过调用 Pattern
对象的 matcher
方法来创建一个 Matcher
对象。
Pattern pattern = Pattern.compile("[a-z]+");
Matcher matcher = pattern.matcher("hello");
在上述代码中,我们创建了一个 Matcher
对象,用于匹配字符串 “hello”。
Matcher
类提供了一些方法来进行匹配操作,例如:
matches()
:尝试将整个区域与模式进行匹配。find()
:尝试查找与模式匹配的输入序列的下一个子序列。group()
:返回由以前匹配操作所匹配的输入子序列。start()
:返回以前匹配的初始索引。end()
:返回最后匹配字符之后的索引。
这就是Java正则表达式的实现,主要由 Pattern
类和 Matcher
类完成。
Java正则表达式的性能优化
在Java中,正则表达式是一个强大而灵活的工具,但如果不正确使用,可能会导致性能问题。以下是一些优化Java正则表达式性能的建议:
预编译正则表达式
在Java中,每次调用 String
类的 matches()
, split()
, replaceAll()
等方法时,都会编译正则表达式,这是一个相对耗时的操作。如果你在循环或频繁调用的方法中使用这些方法,应考虑预编译正则表达式。
预编译正则表达式意味着你创建一个 Pattern
对象,然后重复使用它。这样,正则表达式只需要编译一次,而不是每次使用时都编译。
Pattern pattern = Pattern.compile("[a-z]+");
for (String input : inputs) {
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
// do something
}
}
在上述代码中,正则表达式只编译了一次,然后在循环中重复使用。
使用非捕获组
在正则表达式中,使用括号 ()
可以创建一个组,这意味着正则表达式引擎会记住与括号中的表达式匹配的文本。然而,这个记忆功能有时是不必要的,而且会消耗额外的资源。
如果你不需要记住匹配的文本,可以使用非捕获组 (?:)
代替普通组。非捕获组的语法是在括号内的第一个字符位置使用 ?:
。
比如,(?:[a-z]+)\d*
匹配一个或多个小写字母后面跟着零个或多个数字,但是不会记住匹配的字母。
这两个优化技巧可以帮助你提高Java正则表达式的性能。然而,你应该注意,正则表达式不总是解决问题的最佳工具。在某些情况下,使用其他的字符串处理方法可能更有效。