|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
正则表达式(Regular Expression)是一种强大的文本处理工具,它使用特定的字符序列来描述和匹配字符串模式。在Java开发中,正则表达式是处理文本数据的必备技能,无论是数据验证、信息提取还是文本替换,正则表达式都能提供简洁而高效的解决方案。
随着大数据和文本分析的发展,掌握正则表达式已成为Java开发者的核心竞争力之一。本文将从基础语法开始,逐步深入到高级应用技巧,帮助读者全面掌握正则表达式在Java字符串操作中的应用,解决实际开发中的文本处理难题。
正则表达式基础语法
字符类
字符类是正则表达式中最基本的构建块,用于匹配特定类型的字符。
• 普通字符:大多数字符(如字母、数字)会直接匹配自身。例如,正则表达式"Java"会匹配字符串中的”Java”。
• 简单类:使用方括号[]定义字符集,匹配其中的任意一个字符。例如,[abc]匹配”a”、”b”或”c”。
• 否定类:使用[^]定义否定字符集,匹配不在其中的任意字符。例如,[^abc]匹配除”a”、”b”、”c”外的任意字符。
• 范围类:使用连字符-定义字符范围。例如,[a-z]匹配任意小写字母,[0-9]匹配任意数字。
• 预定义类:一些常用的字符类有简写形式:\d:数字字符,等同于[0-9]\D:非数字字符,等同于[^0-9]\w:单词字符(字母、数字、下划线),等同于[a-zA-Z0-9_]\W:非单词字符,等同于[^a-zA-Z0-9_]\s:空白字符(空格、制表符、换行符等)\S:非空白字符
• \d:数字字符,等同于[0-9]
• \D:非数字字符,等同于[^0-9]
• \w:单词字符(字母、数字、下划线),等同于[a-zA-Z0-9_]
• \W:非单词字符,等同于[^a-zA-Z0-9_]
• \s:空白字符(空格、制表符、换行符等)
• \S:非空白字符
• \d:数字字符,等同于[0-9]
• \D:非数字字符,等同于[^0-9]
• \w:单词字符(字母、数字、下划线),等同于[a-zA-Z0-9_]
• \W:非单词字符,等同于[^a-zA-Z0-9_]
• \s:空白字符(空格、制表符、换行符等)
• \S:非空白字符
- // 示例:使用字符类匹配
- String text = "The price is $123.45";
- // 匹配所有数字
- Pattern pattern = Pattern.compile("\\d+");
- Matcher matcher = pattern.matcher(text);
- while (matcher.find()) {
- System.out.println("Found number: " + matcher.group());
- }
- // 输出: Found number: 123
- // 输出: Found number: 45
复制代码
量词
量词用于指定前面的字符或字符组出现的次数。
• *:零次或多次
• +:一次或多次
• ?:零次或一次
• {n}:恰好n次
• {n,}:至少n次
• {n,m}:至少n次,至多m次
- // 示例:使用量词匹配
- String text = "a abc abcd abcde";
- // 匹配以a开头,后面跟着1到3个b的字符串
- Pattern pattern = Pattern.compile("ab{1,3}");
- Matcher matcher = pattern.matcher(text);
- while (matcher.find()) {
- System.out.println("Found match: " + matcher.group());
- }
- // 输出: Found match: ab
- // 输出: Found match: abb
- // 输出: Found match: abbb
复制代码
边界匹配
边界匹配用于指定匹配位置,而不是字符本身。
• ^:行的开始
• $:行的结束
• \b:单词边界
• \B:非单词边界
• \A:输入的开始
• \Z:输入的结束(不包括最后的终止符)
• \z:输入的结束(包括最后的终止符)
- // 示例:使用边界匹配
- String text = "Java is a programming language. Java is widely used.";
- // 匹配句子开头的Java
- Pattern pattern = Pattern.compile("^Java");
- Matcher matcher = pattern.matcher(text);
- if (matcher.find()) {
- System.out.println("Found Java at start: " + matcher.group());
- }
- // 输出: Found Java at start: Java
- // 匹配单词边界上的Java
- pattern = Pattern.compile("\\bJava\\b");
- matcher = pattern.matcher(text);
- while (matcher.find()) {
- System.out.println("Found Java as whole word: " + matcher.group());
- }
- // 输出: Found Java as whole word: Java
- // 输出: Found Java as whole word: Java
复制代码
分组和捕获
分组使用圆括号()实现,可以将多个字符作为一个单元,并可以捕获匹配的文本。
• (pattern):捕获组,将匹配的文本捕获起来
• (?:pattern):非捕获组,不捕获匹配的文本
• (?<name>pattern):命名捕获组,给捕获组命名
• \n:反向引用,引用第n个捕获组匹配的内容
• \k<name>:命名反向引用,引用指定名称的捕获组
- // 示例:使用分组和捕获
- String text = "John Smith, Alice Johnson, Bob Brown";
- // 匹配名字和姓氏,并捕获
- Pattern pattern = Pattern.compile("(\\w+)\\s+(\\w+)");
- Matcher matcher = pattern.matcher(text);
- while (matcher.find()) {
- System.out.println("Full name: " + matcher.group());
- System.out.println("First name: " + matcher.group(1));
- System.out.println("Last name: " + matcher.group(2));
- }
- /*
- 输出:
- Full name: John Smith
- First name: John
- Last name: Smith
- Full name: Alice Johnson
- First name: Alice
- Last name: Johnson
- Full name: Bob Brown
- First name: Bob
- Last name: Brown
- */
复制代码
Java中的正则表达式API
Java提供了丰富的API来支持正则表达式操作,主要包括java.util.regex包中的Pattern和Matcher类,以及String类中的便捷方法。
Pattern类
Pattern类表示编译后的正则表达式模式,它是不可变的,可以被多个Matcher实例共享。
• Pattern.compile(String regex):编译正则表达式,创建Pattern对象
• Pattern.compile(String regex, int flags):编译正则表达式,并指定匹配标志
• Pattern.matches(String regex, CharSequence input):快速匹配整个输入序列
• Pattern.quote(String s):返回指定字符串的字面量模式字符串
• Pattern.split(CharSequence input):使用模式分割输入序列
常用的匹配标志包括:
• Pattern.CASE_INSENSITIVE:启用不区分大小写的匹配
• Pattern.MULTILINE:启用多行模式,^和$匹配行的开始和结束
• Pattern.DOTALL:启用点全部模式,.匹配包括行结束符在内的所有字符
• Pattern.UNICODE_CASE:启用Unicode感知的大小写折叠
• Pattern.CANON_EQ:启用规范等价
- // 示例:使用Pattern类
- String text = "Cat\nDog\nBird\nfish";
- // 编译不区分大小写的模式
- Pattern pattern = Pattern.compile("cat", Pattern.CASE_INSENSITIVE);
- Matcher matcher = pattern.matcher(text);
- while (matcher.find()) {
- System.out.println("Found match: " + matcher.group());
- }
- // 输出: Found match: Cat
- // 使用多行模式匹配行首
- pattern = Pattern.compile("^[A-Z]", Pattern.MULTILINE);
- matcher = pattern.matcher(text);
- while (matcher.find()) {
- System.out.println("Found capital at line start: " + matcher.group());
- }
- /*
- 输出:
- Found capital at line start: C
- Found capital at line start: D
- Found capital at line start: B
- */
复制代码
Matcher类
Matcher类是对输入字符串进行解释和匹配操作的引擎。
• matcher.matches():尝试将整个区域与模式匹配
• matcher.lookingAt():尝试从区域开头开始匹配模式
• matcher.find():尝试查找与模式匹配的输入序列的下一个子序列
• matcher.find(int start):重置匹配器,然后从指定索引开始尝试查找匹配的子序列
• matcher.group():返回上一个匹配的结果
• matcher.group(int group):返回上一个匹配操作中指定组所匹配的输入子序列
• matcher.group(String name):返回上一个匹配操作中指定命名组所匹配的输入子序列
• matcher.start():返回上一个匹配的开始索引
• matcher.end():返回上一个匹配的结束索引
• matcher.replaceAll(String replacement):将所有匹配的子序列替换为指定的替换字符串
• matcher.replaceFirst(String replacement):将第一个匹配的子序列替换为指定的替换字符串
• matcher.appendReplacement(StringBuffer sb, String replacement):实现非终端的添加和替换步骤
• matcher.appendTail(StringBuffer sb):实现终端的添加和替换步骤
- // 示例:使用Matcher类
- String text = "The quick brown fox jumps over the lazy dog.";
- Pattern pattern = Pattern.compile("\\b\\w{4}\\b"); // 匹配4个字母的单词
- Matcher matcher = pattern.matcher(text);
- // 查找所有匹配
- while (matcher.find()) {
- System.out.println("Found 4-letter word: " + matcher.group() +
- " at position " + matcher.start() + "-" + matcher.end());
- }
- /*
- 输出:
- Found 4-letter word: over at position 26-30
- Found 4-letter word: lazy at position 35-39
- */
- // 替换所有匹配
- String result = matcher.replaceAll("****");
- System.out.println("After replacement: " + result);
- // 输出: After replacement: The quick brown fox jumps **** the **** dog.
复制代码
String类中的正则方法
String类提供了一些便捷方法,可以直接使用正则表达式进行操作。
• String.matches(String regex):判断字符串是否匹配给定的正则表达式
• String.split(String regex):根据正则表达式分割字符串
• String.split(String regex, int limit):根据正则表达式分割字符串,限制分割次数
• String.replaceFirst(String regex, String replacement):替换第一个匹配的子序列
• String.replaceAll(String regex, String replacement):替换所有匹配的子序列
- // 示例:使用String类中的正则方法
- String text = "apple,banana,orange,grape";
- // 使用split方法分割字符串
- String[] fruits = text.split(",");
- System.out.println("Fruits array: " + Arrays.toString(fruits));
- // 输出: Fruits array: [apple, banana, orange, grape]
- // 使用matches方法验证格式
- String email = "user@example.com";
- if (email.matches("[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}")) {
- System.out.println("Valid email format");
- } else {
- System.out.println("Invalid email format");
- }
- // 输出: Valid email format
- // 使用replaceAll方法替换文本
- String result = text.replaceAll("[aeiou]", "*");
- System.out.println("After replacing vowels: " + result);
- // 输出: After replacing vowels: *ppl*,b*n*n*,*r*ng*,gr*p*
复制代码
常见字符串操作应用
验证
正则表达式常用于验证输入数据是否符合特定格式,如电子邮件、电话号码、日期等。
- // 示例:电子邮件验证
- public class EmailValidator {
- private static final String EMAIL_REGEX =
- "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
-
- public static boolean isValidEmail(String email) {
- if (email == null) {
- return false;
- }
- return email.matches(EMAIL_REGEX);
- }
-
- public static void main(String[] args) {
- String[] emails = {
- "user@example.com",
- "user.name@example.com",
- "user-name@example.co.uk",
- "user@subdomain.example.com",
- "invalid.email",
- "@example.com",
- "user@.com"
- };
-
- for (String email : emails) {
- System.out.println(email + " is " + (isValidEmail(email) ? "valid" : "invalid"));
- }
- }
- }
- /*
- 输出:
- user@example.com is valid
- user.name@example.com is valid
- user-name@example.co.uk is valid
- user@subdomain.example.com is valid
- invalid.email is invalid
- @example.com is invalid
- user@.com is invalid
- */
复制代码- // 示例:电话号码验证
- public class PhoneNumberValidator {
- // 支持多种格式:(123) 456-7890, 123-456-7890, 123.456.7890, 1234567890
- private static final String PHONE_REGEX =
- "^(\\(\\d{3}\\)\\s|\\d{3}[-.]?)?\\d{3}[-.]?\\d{4}$";
-
- public static boolean isValidPhoneNumber(String phoneNumber) {
- if (phoneNumber == null) {
- return false;
- }
- return phoneNumber.matches(PHONE_REGEX);
- }
-
- public static void main(String[] args) {
- String[] phoneNumbers = {
- "(123) 456-7890",
- "123-456-7890",
- "123.456.7890",
- "1234567890",
- "123 456 7890",
- "12-345-67890"
- };
-
- for (String phoneNumber : phoneNumbers) {
- System.out.println(phoneNumber + " is " +
- (isValidPhoneNumber(phoneNumber) ? "valid" : "invalid"));
- }
- }
- }
- /*
- 输出:
- (123) 456-7890 is valid
- 123-456-7890 is valid
- 123.456.7890 is valid
- 1234567890 is valid
- 123 456 7890 is invalid
- 12-345-67890 is invalid
- */
复制代码
查找
正则表达式可以用于在文本中查找符合特定模式的内容。
- // 示例:查找URL
- public class URLFinder {
- public static void findURLs(String text) {
- // 简单的URL正则表达式
- String urlRegex = "(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
-
- Pattern pattern = Pattern.compile(urlRegex);
- Matcher matcher = pattern.matcher(text);
-
- System.out.println("Found URLs:");
- while (matcher.find()) {
- System.out.println(matcher.group());
- }
- }
-
- public static void main(String[] args) {
- String text = "Visit our website at https://www.example.com or " +
- "check out our blog at http://blog.example.com/post?id=123. " +
- "You can also download files from ftp://files.example.com/data.zip.";
-
- findURLs(text);
- }
- }
- /*
- 输出:
- Found URLs:
- https://www.example.com
- http://blog.example.com/post?id=123
- ftp://files.example.com/data.zip
- */
复制代码
替换
正则表达式可以用于替换文本中符合特定模式的内容。
- // 示例:敏感信息脱敏
- public class DataMasker {
- public static String maskEmail(String email) {
- return email.replaceAll("(^[^@]+)|(@[^@]+$)",
- match -> match.group(1) != null ?
- match.group(1).substring(0, Math.min(2, match.group(1).length())) + "*****" :
- match.group(2));
- }
-
- public static String maskPhoneNumber(String phoneNumber) {
- return phoneNumber.replaceAll("\\d(?=\\d{4})", "*");
- }
-
- public static String maskCreditCard(String cardNumber) {
- return cardNumber.replaceAll("\\d(?=\\d{4})", "*");
- }
-
- public static void main(String[] args) {
- String email = "john.doe@example.com";
- String phoneNumber = "123-456-7890";
- String creditCard = "1234-5678-9012-3456";
-
- System.out.println("Original email: " + email);
- System.out.println("Masked email: " + maskEmail(email));
-
- System.out.println("Original phone: " + phoneNumber);
- System.out.println("Masked phone: " + maskPhoneNumber(phoneNumber));
-
- System.out.println("Original card: " + creditCard);
- System.out.println("Masked card: " + maskCreditCard(creditCard));
- }
- }
- /*
- 输出:
- Original email: john.doe@example.com
- Masked email: jo*****@example.com
- Original phone: 123-456-7890
- Masked phone: ***-***-7890
- Original card: 1234-5678-9012-3456
- Masked card: ****-****-****-3456
- */
复制代码
分割
正则表达式可以用于根据特定模式分割字符串。
- // 示例:CSV解析
- public class CSVParser {
- public static String[] parseCSVLine(String line) {
- // 处理带引号的CSV字段
- String csvRegex = ",(?=(?:[^"]*"[^"]*")*[^"]*$)";
- return line.split(csvRegex);
- }
-
- public static void main(String[] args) {
- String csvLine = "John,Doe,"New York, NY",30,"$50,000"";
- String[] fields = parseCSVLine(csvLine);
-
- System.out.println("CSV fields:");
- for (int i = 0; i < fields.length; i++) {
- System.out.println((i + 1) + ": " + fields[i]);
- }
- }
- }
- /*
- 输出:
- CSV fields:
- 1: John
- 2: Doe
- 3: "New York, NY"
- 4: 30
- 5: "$50,000"
- */
复制代码
高级正则表达式技巧
贪婪与 reluctant 量词
量词默认是贪婪的(greedy),会尽可能多地匹配字符。 reluctant 量词(也称为非贪婪或懒惰量词)会尽可能少地匹配字符。
• 贪婪量词:*,+,?,{n},{n,},{n,m}
• Reluctant量词:*?,+?,??,{n}?,{n,}?,{n,m}?
• Possessive量词:*+,++,?+,{n}+,{n,}+,{n,m}+
- // 示例:贪婪与reluctant量词
- public class QuantifierExample {
- public static void main(String[] args) {
- String text = "<div>Content 1</div><div>Content 2</div>";
-
- // 贪婪匹配 - 匹配尽可能多的字符
- Pattern greedyPattern = Pattern.compile("<div>.*</div>");
- Matcher greedyMatcher = greedyPattern.matcher(text);
- if (greedyMatcher.find()) {
- System.out.println("Greedy match: " + greedyMatcher.group());
- }
- // 输出: Greedy match: <div>Content 1</div><div>Content 2</div>
-
- // Reluctant匹配 - 匹配尽可能少的字符
- Pattern reluctantPattern = Pattern.compile("<div>.*?</div>");
- Matcher reluctantMatcher = reluctantPattern.matcher(text);
- while (reluctantMatcher.find()) {
- System.out.println("Reluctant match: " + reluctantMatcher.group());
- }
- /*
- 输出:
- Reluctant match: <div>Content 1</div>
- Reluctant match: <div>Content 2</div>
- */
- }
- }
复制代码
零宽断言
零宽断言(zero-width assertions)用于匹配某个位置,而不是字符本身。它们不消耗字符,只进行判断。
• 正向先行断言(Positive Lookahead):(?=pattern),断言当前位置后面能匹配pattern
• 负向先行断言(Negative Lookahead):(?!pattern),断言当前位置后面不能匹配pattern
• 正向后行断言(Positive Lookbehind):(?<=pattern),断言当前位置前面能匹配pattern
• 负向后行断言(Negative Lookbehind):(?<!pattern),断言当前位置前面不能匹配pattern
- // 示例:使用零宽断言
- public class ZeroWidthAssertionExample {
- public static void main(String[] args) {
- String text = "apple banana orange grape kiwi";
-
- // 正向先行断言 - 匹配后面跟着空格的单词
- Pattern lookaheadPattern = Pattern.compile("\\w+(?=\\s)");
- Matcher lookaheadMatcher = lookaheadPattern.matcher(text);
- System.out.println("Positive lookahead matches:");
- while (lookaheadMatcher.find()) {
- System.out.println(lookaheadMatcher.group());
- }
- /*
- 输出:
- Positive lookahead matches:
- apple
- banana
- orange
- grape
- */
-
- // 负向先行断言 - 匹配后面不跟着空格的单词
- Pattern negativeLookaheadPattern = Pattern.compile("\\w+(?!\\s)");
- Matcher negativeLookaheadMatcher = negativeLookaheadPattern.matcher(text);
- System.out.println("\nNegative lookahead matches:");
- while (negativeLookaheadMatcher.find()) {
- System.out.println(negativeLookaheadMatcher.group());
- }
- /*
- 输出:
- Negative lookahead matches:
- e
- a
- e
- e
- kiwi
- */
-
- // 正向后行断言 - 匹配前面是空格的单词
- Pattern lookbehindPattern = Pattern.compile("(?<=\\s)\\w+");
- Matcher lookbehindMatcher = lookbehindPattern.matcher(text);
- System.out.println("\nPositive lookbehind matches:");
- while (lookbehindMatcher.find()) {
- System.out.println(lookbehindMatcher.group());
- }
- /*
- 输出:
- Positive lookbehind matches:
- banana
- orange
- grape
- kiwi
- */
- }
- }
复制代码
回溯与性能优化
正则表达式引擎使用回溯(backtracking)来尝试不同的匹配路径。复杂的正则表达式可能导致大量的回溯,影响性能。
- // 示例:正则表达式性能优化
- public class RegexPerformanceExample {
- public static void main(String[] args) {
- String text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaX";
-
- // 容易导致灾难性回溯的正则表达式
- String badRegex = "(a+)+";
- long startTime = System.currentTimeMillis();
- Pattern badPattern = Pattern.compile(badRegex);
- Matcher badMatcher = badPattern.matcher(text);
- if (badMatcher.matches()) {
- System.out.println("Bad regex matched");
- }
- long endTime = System.currentTimeMillis();
- System.out.println("Bad regex time: " + (endTime - startTime) + "ms");
-
- // 优化后的正则表达式
- String goodRegex = "a+";
- startTime = System.currentTimeMillis();
- Pattern goodPattern = Pattern.compile(goodRegex);
- Matcher goodMatcher = goodPattern.matcher(text);
- if (goodMatcher.matches()) {
- System.out.println("Good regex matched");
- }
- endTime = System.currentTimeMillis();
- System.out.println("Good regex time: " + (endTime - startTime) + "ms");
- }
- }
- /*
- 输出:
- Bad regex matched
- Bad regex time: 15ms
- Good regex matched
- Good regex time: 0ms
- */
复制代码
正则表达式性能优化技巧:
1. 避免嵌套量词,如(a+)+
2. 使用更具体的字符类,如[0-9]代替\d
3. 使用占有量词(possessive quantifiers)避免不必要的回溯,如a++代替a+
4. 使用非捕获组(?:pattern)代替捕获组(pattern)
5. 预编译正则表达式并重用Pattern对象
6. 使用锚点^和$限制匹配范围
7. 避免过度使用通配符.*
实际开发案例
表单验证
在实际开发中,表单验证是正则表达式的常见应用场景。
- // 示例:用户注册表单验证
- public class RegistrationValidator {
- // 用户名:4-16个字符,只能包含字母、数字和下划线
- private static final String USERNAME_REGEX = "^[a-zA-Z0-9_]{4,16}$";
-
- // 密码:至少8个字符,至少包含一个大写字母、一个小写字母、一个数字和一个特殊字符
- private static final String PASSWORD_REGEX =
- "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
-
- // 邮箱:标准邮箱格式
- private static final String EMAIL_REGEX =
- "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
-
- // 手机号:简单验证,11位数字
- private static final String PHONE_REGEX = "^\\d{11}$";
-
- public static boolean validateUsername(String username) {
- return username != null && username.matches(USERNAME_REGEX);
- }
-
- public static boolean validatePassword(String password) {
- return password != null && password.matches(PASSWORD_REGEX);
- }
-
- public static boolean validateEmail(String email) {
- return email != null && email.matches(EMAIL_REGEX);
- }
-
- public static boolean validatePhone(String phone) {
- return phone != null && phone.matches(PHONE_REGEX);
- }
-
- public static void main(String[] args) {
- String username = "john_doe123";
- String password = "SecurePass123!";
- String email = "john.doe@example.com";
- String phone = "12345678901";
-
- System.out.println("Username " + username + " is " +
- (validateUsername(username) ? "valid" : "invalid"));
- System.out.println("Password is " +
- (validatePassword(password) ? "valid" : "invalid"));
- System.out.println("Email " + email + " is " +
- (validateEmail(email) ? "valid" : "invalid"));
- System.out.println("Phone " + phone + " is " +
- (validatePhone(phone) ? "valid" : "invalid"));
- }
- }
- /*
- 输出:
- Username john_doe123 is valid
- Password is valid
- Email john.doe@example.com is valid
- Phone 12345678901 is valid
- */
复制代码
日志分析
日志分析是正则表达式的另一个重要应用场景,可以从大量日志中提取有用信息。
- // 示例:Web服务器日志分析
- public class LogAnalyzer {
- // Apache Common Log Format: IP - - [date] "request" status size
- private static final String LOG_REGEX =
- "^(\\S+) \\S+ \\S+ \\[([^\\]]+)\\] "([^"]+)" (\\d+) (\\S+)$";
-
- public static void analyzeLog(String logLine) {
- Pattern pattern = Pattern.compile(LOG_REGEX);
- Matcher matcher = pattern.matcher(logLine);
-
- if (matcher.matches()) {
- String ip = matcher.group(1);
- String timestamp = matcher.group(2);
- String request = matcher.group(3);
- int status = Integer.parseInt(matcher.group(4));
- String size = matcher.group(5);
-
- System.out.println("IP: " + ip);
- System.out.println("Timestamp: " + timestamp);
- System.out.println("Request: " + request);
- System.out.println("Status: " + status);
- System.out.println("Size: " + size);
-
- // 分析请求类型
- String requestType = request.split(" ")[0];
- System.out.println("Request Type: " + requestType);
-
- // 分析请求路径
- String[] requestParts = request.split(" ");
- if (requestParts.length > 1) {
- String path = requestParts[1];
- System.out.println("Path: " + path);
-
- // 提取文件扩展名
- if (path.contains(".")) {
- String extension = path.substring(path.lastIndexOf('.') + 1);
- System.out.println("File Extension: " + extension);
- }
- }
-
- // 分析状态码
- if (status >= 400) {
- System.out.println("*** ERROR REQUEST ***");
- }
- } else {
- System.out.println("Invalid log format");
- }
- }
-
- public static void main(String[] args) {
- String logLine = "192.168.1.1 - - [25/Dec/2022:10:00:00 +0000] "GET /index.html HTTP/1.1" 200 1234";
- analyzeLog(logLine);
- }
- }
- /*
- 输出:
- IP: 192.168.1.1
- Timestamp: 25/Dec/2022:10:00:00 +0000
- Request: GET /index.html HTTP/1.1
- Status: 200
- Size: 1234
- Request Type: GET
- Path: /index.html
- File Extension: html
- */
复制代码
数据提取
从文本中提取结构化数据是正则表达式的强项。
- // 示例:从HTML中提取链接
- public class LinkExtractor {
- public static List<String> extractLinks(String html) {
- List<String> links = new ArrayList<>();
-
- // 匹配<a>标签中的href属性
- String linkRegex = "<a\\s+[^>]*href\\s*=\\s*['"]([^'"]+)['"][^>]*>";
- Pattern pattern = Pattern.compile(linkRegex);
- Matcher matcher = pattern.matcher(html);
-
- while (matcher.find()) {
- links.add(matcher.group(1));
- }
-
- return links;
- }
-
- public static void main(String[] args) {
- String html = "<html><body>" +
- "<a href='https://www.example.com'>Example</a>" +
- "<a href="/about">About Us</a>" +
- "<a href='contact.html'>Contact</a>" +
- "<a href = "https://www.google.com">Google</a>" +
- "</body></html>";
-
- List<String> links = extractLinks(html);
- System.out.println("Extracted links:");
- for (String link : links) {
- System.out.println(link);
- }
- }
- }
- /*
- 输出:
- Extracted links:
- https://www.example.com
- /about
- contact.html
- https://www.google.com
- */
复制代码
文本处理
文本处理是正则表达式的经典应用场景,包括清理、格式化和转换文本。
- // 示例:文本清理和标准化
- public class TextCleaner {
- // 移除HTML标签
- public static String removeHtmlTags(String html) {
- return html.replaceAll("<[^>]*>", "");
- }
-
- // 标准化空白字符
- public static String normalizeWhitespace(String text) {
- return text.replaceAll("\\s+", " ").trim();
- }
-
- // 移除特殊字符,只保留字母、数字和空格
- public static String removeSpecialChars(String text) {
- return text.replaceAll("[^a-zA-Z0-9\\s]", "");
- }
-
- // 转换为标题格式(每个单词首字母大写)
- public static String toTitleCase(String text) {
- return text.replaceAll("\\b(\\w)(\\w*)\\b",
- match -> match.group(1).toUpperCase() + match.group(2).toLowerCase());
- }
-
- public static void main(String[] args) {
- String html = "<p>This is a <b>sample</b> text with <i>HTML</i> tags.</p>";
- System.out.println("Original HTML: " + html);
- System.out.println("Without HTML tags: " + removeHtmlTags(html));
-
- String messyText = "This has multiple spaces and\ttabs\nand\nnewlines";
- System.out.println("\nOriginal text: " + messyText);
- System.out.println("Normalized text: " + normalizeWhitespace(messyText));
-
- String specialCharsText = "Hello! @World# $123%";
- System.out.println("\nOriginal text: " + specialCharsText);
- System.out.println("Without special chars: " + removeSpecialChars(specialCharsText));
-
- String titleText = "this is a TITLE";
- System.out.println("\nOriginal text: " + titleText);
- System.out.println("Title case: " + toTitleCase(titleText));
- }
- }
- /*
- 输出:
- Original HTML: <p>This is a <b>sample</b> text with <i>HTML</i> tags.</p>
- Without HTML tags: This is a sample text with HTML tags.
- Original text: This has multiple spaces and tabs
- and
- newlines
- Normalized text: This has multiple spaces and tabs and newlines
- Original text: Hello! @World# $123%
- Without special chars: Hello World 123
- Original text: this is a TITLE
- Title case: This Is A Title
- */
复制代码
最佳实践与常见陷阱
最佳实践
1. 预编译正则表达式:对于频繁使用的正则表达式,应该预编译并重用Pattern对象,而不是每次使用都重新编译。
- // 不好的做法
- public boolean isValidEmail(String email) {
- return email.matches("^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$");
- }
- // 好的做法
- private static final Pattern EMAIL_PATTERN =
- Pattern.compile("^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$");
- public boolean isValidEmail(String email) {
- return email != null && EMAIL_PATTERN.matcher(email).matches();
- }
复制代码
1. 使用适当的匹配方法:根据需求选择合适的匹配方法,如matches()、lookingAt()或find()。
- String text = "The price is $123.45";
- Pattern pattern = Pattern.compile("\\d+");
- // matches()尝试将整个区域与模式匹配
- boolean fullMatch = pattern.matcher(text).matches(); // 返回false
- // lookingAt()尝试从区域开头开始匹配模式
- boolean startMatch = pattern.matcher(text).lookingAt(); // 返回false
- // find()尝试查找与模式匹配的输入序列的下一个子序列
- boolean partialMatch = pattern.matcher(text).find(); // 返回true
复制代码
1. 使用非捕获组提高性能:如果不需要捕获匹配的文本,使用非捕获组(?:pattern)代替捕获组(pattern)。
- // 不好的做法
- String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
- // 好的做法(如果不需要捕获分组)
- String regex = "(?:\\d{4})-(?:\\d{2})-(?:\\d{2})";
复制代码
1. 使用命名捕获组提高可读性:对于复杂的正则表达式,使用命名捕获组(?<name>pattern)代替数字索引。
- // 不好的做法
- String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
- Matcher matcher = Pattern.compile(regex).matcher("2023-01-01");
- if (matcher.matches()) {
- String year = matcher.group(1);
- String month = matcher.group(2);
- String day = matcher.group(3);
- }
- // 好的做法
- String regex = "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})";
- Matcher matcher = Pattern.compile(regex).matcher("2023-01-01");
- if (matcher.matches()) {
- String year = matcher.group("year");
- String month = matcher.group("month");
- String day = matcher.group("day");
- }
复制代码
常见陷阱
1. 忽略转义字符:在Java字符串中,反斜杠\需要转义为\\,因此正则表达式中的\d在Java字符串中应写成\\d。
- // 不好的做法
- String regex = "\d+"; // 编译错误
- // 好的做法
- String regex = "\\d+";
复制代码
1. 过度使用通配符:过度使用.*或.+可能导致性能问题和意外匹配。
- // 不好的做法
- String regex = ".*foo.*"; // 可能匹配过多内容
- // 好的做法
- String regex = "\\bfoo\\b"; // 只匹配单词"foo"
复制代码
1. 忽略回溯问题:复杂的正则表达式可能导致灾难性回溯,影响性能。
- // 不好的做法,可能导致灾难性回溯
- String regex = "(a+)+";
- // 好的做法
- String regex = "a+";
复制代码
1. 忽略输入验证:在使用正则表达式处理用户输入前,应该验证输入是否为null或空。
- // 不好的做法
- public boolean isValid(String input) {
- return input.matches("^[a-z]+$");
- }
- // 好的做法
- public boolean isValid(String input) {
- return input != null && !input.isEmpty() && input.matches("^[a-z]+$");
- }
复制代码
总结
正则表达式是Java中处理文本数据的强大工具,通过掌握其基础语法和高级技巧,开发者可以高效地解决各种文本处理问题。本文从正则表达式的基础语法开始,介绍了Java中的正则表达式API,探讨了常见的字符串操作应用,并分享了高级技巧和最佳实践。
在实际开发中,正则表达式广泛应用于表单验证、日志分析、数据提取和文本处理等场景。通过预编译正则表达式、使用非捕获组、命名捕获组等技巧,可以提高正则表达式的性能和可读性。同时,避免常见陷阱,如忽略转义字符、过度使用通配符、忽略回溯问题等,可以确保正则表达式的正确性和效率。
掌握正则表达式是Java开发者的必备技能,通过不断实践和学习,开发者可以充分利用正则表达式的强大功能,解决实际开发中的文本处理难题。 |
|