Java Matcher.appendTail 方法
上次修改时间:2025 年 4 月 20 日
appendTail
方法是 Java 的 java.util.regex.Matcher
类的一部分。它在最后一次匹配后将剩余的输入序列追加到 StringBuffer 中。此方法通常与 appendReplacement
结合使用,用于基于正则表达式的字符串操作。
在使用正则表达式进行查找和替换操作时,appendTail
确保将最后一次匹配之后的任何文本包含在结果中。它完成了由 appendReplacement
调用开始的转换过程。
Matcher.appendTail 概述
appendTail
方法的签名是:public StringBuffer appendTail(StringBuffer sb)
。它将剩余的子字符串追加到指定的 StringBuffer 并返回该缓冲区。
当在循环中使用 appendReplacement
时,此方法至关重要。在处理完所有匹配项后,appendTail
会添加最后一个匹配项之后的任何剩余文本。如果没有它,这部分文本将会丢失。
appendTail 的基本用法
此示例演示了 appendTail
与 appendReplacement
的基本用法。我们将把字符串中的所有数字替换成它们的单词等价物,同时保留其余文本。
package com.zetcode; import java.util.regex.*; public class AppendTailBasic { public static void main(String[] args) { String input = "I have 3 apples and 5 oranges."; Pattern pattern = Pattern.compile("\\d+"); Matcher matcher = pattern.matcher(input); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String numWord = convertToWord(matcher.group()); matcher.appendReplacement(sb, numWord); } matcher.appendTail(sb); System.out.println("Original: " + input); System.out.println("Modified: " + sb.toString()); } private static String convertToWord(String num) { switch(num) { case "3": return "three"; case "5": return "five"; default: return num; } } }
在此示例中,我们首先使用 matcher.find
找到所有数字序列。对于每个匹配项,我们将数字转换为其单词形式,并使用 appendReplacement
。循环结束后,appendTail
将剩余文本 (" oranges.") 添加到结果中。
输出显示了完整的转换字符串,其中所有数字都被替换,并且保留了剩余文本。如果没有 appendTail
,字符串的最后一部分将会丢失。
使用 appendTail 进行多次替换
此示例展示了如何在文本中执行多次替换,同时保持原始结构。我们将替换财务报表中的日期和货币值。
package com.zetcode; import java.util.regex.*; public class MultipleReplacements { public static void main(String[] args) { String statement = "Date: 12/31/2023, Amount: $1,250.75\n" + "Date: 01/15/2024, Amount: $950.50"; Pattern datePattern = Pattern.compile("\\d{2}/\\d{2}/\\d{4}"); Pattern amountPattern = Pattern.compile("\\$\\d{1,3}(,\\d{3})*\\.\\d{2}"); Matcher matcher = datePattern.matcher(statement); StringBuffer sb = new StringBuffer(); // Replace dates first while (matcher.find()) { String newDate = formatDate(matcher.group()); matcher.appendReplacement(sb, newDate); } matcher.appendTail(sb); // Now replace amounts in the modified string String intermediate = sb.toString(); matcher = amountPattern.matcher(intermediate); sb = new StringBuffer(); while (matcher.find()) { String newAmount = formatAmount(matcher.group()); matcher.appendReplacement(sb, newAmount); } matcher.appendTail(sb); System.out.println("Original:\n" + statement); System.out.println("\nModified:\n" + sb.toString()); } private static String formatDate(String date) { return date.replaceAll("(\\d{2})/(\\d{2})/(\\d{4})", "$2-$1-$3"); } private static String formatAmount(String amount) { return amount.replace("$", "").replace(",", "") + " USD"; } }
此示例执行两个连续的替换操作。首先,它将日期从 MM/DD/YYYY 重新格式化为 DD-MM-YYYY。然后,它将货币金额从 $1,250.75 格式转换为 1250.75 USD 格式。
在每次替换操作之后,appendTail
确保保留所有剩余文本。中间结果被存储并用作下一个替换操作的输入。
appendTail 与部分替换
有时我们只想替换某些匹配项,而保留其他匹配项不变。此示例演示了选择性替换,同时仍使用 appendTail
来维护完整的输出。
package com.zetcode; import java.util.regex.*; public class SelectiveReplacement { public static void main(String[] args) { String text = "The colors are red, blue, green, and yellow. " + "I prefer red and blue over green and yellow."; Pattern colorPattern = Pattern.compile("red|blue|green|yellow"); Matcher matcher = colorPattern.matcher(text); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String color = matcher.group(); // Only replace 'red' and 'blue' with their French equivalents if (color.equals("red") || color.equals("blue")) { String replacement = color.equals("red") ? "rouge" : "bleu"; matcher.appendReplacement(sb, replacement); } } matcher.appendTail(sb); System.out.println("Original: " + text); System.out.println("Modified: " + sb.toString()); } }
在此示例中,我们只用法语等价物替换 "red" 和 "blue",而保留 "green" 和 "yellow" 不变。appendTail
调用确保将最后一次匹配之后的任何文本(无论是否替换)都包含在内。
这演示了即使并非所有匹配项都被替换,appendTail
也能正常工作。该方法只是将最后一次匹配位置之后的任何剩余文本追加,而不考虑是否发生了替换。
使用 appendTail 处理 HTML 标签
此示例展示了在处理 HTML 内容时如何使用 appendTail
。我们将删除所有 HTML 标签,同时保留文本内容。
package com.zetcode; import java.util.regex.*; public class HtmlTagRemoval { public static void main(String[] args) { String html = "<html><body><h1>Title</h1>" + "<p>Paragraph with <b>bold</b> text.</p></body></html>"; Pattern tagPattern = Pattern.compile("<[^>]+>"); Matcher matcher = tagPattern.matcher(html); StringBuffer sb = new StringBuffer(); while (matcher.find()) { // Replace each tag with empty string matcher.appendReplacement(sb, ""); } matcher.appendTail(sb); System.out.println("Original HTML:\n" + html); System.out.println("\nText content:\n" + sb.toString()); } }
此代码从字符串中删除所有 HTML 标签,同时保留文本内容。正则表达式模式匹配尖括号之间的任何文本。每个匹配项都被替换为空字符串。
appendTail
调用确保将最后一个 HTML 标签之后的任何文本都包含在结果中。这至关重要,因为 HTML 内容通常在最后一个结束标签之后有文本。
appendTail 与自定义转换
此示例演示了将 appendTail
与复杂转换结合使用。我们将把类似 Markdown 的语法转换为 HTML,展示 appendTail
如何处理完整的转换。
package com.zetcode; import java.util.regex.*; public class MarkdownToHtml { public static void main(String[] args) { String markdown = "# Heading\n" + "This is *italic* and this is **bold**.\n" + "Visit [ZetCode](https://zetcode.cn)."; // Process headings Pattern headingPattern = Pattern.compile("^# (.*)$", Pattern.MULTILINE); Matcher matcher = headingPattern.matcher(markdown); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, "<h1>" + matcher.group(1) + "</h1>"); } matcher.appendTail(sb); String step1 = sb.toString(); // Process italics and bold sb = new StringBuffer(); Pattern emphasisPattern = Pattern.compile("\\*(\\*?)(.*?)\\1\\*"); matcher = emphasisPattern.matcher(step1); while (matcher.find()) { String tag = matcher.group(1).isEmpty() ? "em" : "strong"; matcher.appendReplacement(sb, "<" + tag + ">" + matcher.group(2) + "</" + tag + ">"); } matcher.appendTail(sb); String step2 = sb.toString(); // Process links sb = new StringBuffer(); Pattern linkPattern = Pattern.compile("\\[(.*?)\\]\\((.*?)\\)"); matcher = linkPattern.matcher(step2); while (matcher.find()) { matcher.appendReplacement(sb, "<a href=\"" + matcher.group(2) + "\">" + matcher.group(1) + "</a>"); } matcher.appendTail(sb); System.out.println("Original Markdown:\n" + markdown); System.out.println("\nConverted HTML:\n" + sb.toString()); } }
此示例按顺序执行多个转换:将标题、强调(斜体和粗体)和链接从类似 Markdown 的语法转换为 HTML。每个转换都使用 appendReplacement
和 appendTail
。
中间结果在转换之间传递,appendTail
确保在步骤之间没有内容丢失。这演示了如何使用这些方法构建复杂的文本处理器。
在模板处理中使用 appendTail
此示例展示了如何在模板处理中使用 appendTail
。我们将用实际值替换模板中的占位符,同时保留模板结构。
package com.zetcode; import java.util.regex.*; import java.util.*; public class TemplateProcessor { public static void main(String[] args) { String template = "Dear {{customer}},\n\n" + "Your order #{{orderId}} for {{product}} has shipped.\n" + "Expected delivery date: {{deliveryDate}}.\n\n" + "Thank you for shopping with us!"; Map<String, String> values = new HashMap<>(); values.put("customer", "John Smith"); values.put("orderId", "12345"); values.put("product", "Java Programming Book"); values.put("deliveryDate", "April 25, 2025"); Pattern placeholderPattern = Pattern.compile("\\{\\{(.*?)\\}\\}"); Matcher matcher = placeholderPattern.matcher(template); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String key = matcher.group(1); String replacement = values.getOrDefault(key, "{{" + key + "}}"); matcher.appendReplacement(sb, replacement); } matcher.appendTail(sb); System.out.println("Processed Template:\n" + sb.toString()); } }
此代码处理带有 {{key}}
格式的占位符的模板。每个占位符都由其在映射中的对应值替换。如果找不到键,则占位符保持不变。
appendTail
调用确保将最后一个占位符之后的所有模板文本都包含在输出中。这对于维护完整消息结构(包括结尾文本和格式)至关重要。
appendTail 的性能考虑因素
此示例演示了使用 appendTail
与替代方法的性能影响。我们将比较大型文本处理的字符串构建技术。
package com.zetcode; import java.util.regex.*; public class PerformanceComparison { public static void main(String[] args) { // Generate a large string with many numbers StringBuilder bigText = new StringBuilder(); for (int i = 0; i < 10000; i++) { bigText.append("Number ").append(i).append(", "); } String input = bigText.toString(); // Method 1: Using appendReplacement/appendTail long start1 = System.currentTimeMillis(); Pattern pattern = Pattern.compile("\\d+"); Matcher matcher = pattern.matcher(input); StringBuffer sb1 = new StringBuffer(); while (matcher.find()) { String replacement = "NUM" + matcher.group(); matcher.appendReplacement(sb1, replacement); } matcher.appendTail(sb1); long end1 = System.currentTimeMillis(); // Method 2: Using replaceAll long start2 = System.currentTimeMillis(); String result2 = input.replaceAll("\\d+", "NUM$0"); long end2 = System.currentTimeMillis(); System.out.println("appendReplacement/appendTail time: " + (end1 - start1) + " ms"); System.out.println("replaceAll time: " + (end2 - start2) + " ms"); System.out.println("\nFirst 100 chars of appendTail result:\n" + sb1.substring(0, Math.min(100, sb1.length()))); System.out.println("\nFirst 100 chars of replaceAll result:\n" + result2.substring(0, Math.min(100, result2.length()))); } }
此示例比较了对大型输入字符串使用 appendReplacement
和 appendTail
与 replaceAll
的性能。输入包含许多数字,我们用 "NUM" 作为前缀。 appendTail
方法允许自定义处理每个匹配项,但对于简单的替换,可能比 replaceAll
慢。
时序结果显示了灵活性和性能之间的权衡。对于复杂的转换,使用 appendReplacement
和 appendTail
更通用,而 replaceAll
针对简单的替换进行了优化。开发人员应根据其特定需求进行选择。
来源
在本文中,我们通过实际示例深入探讨了 Matcher.appendTail
方法。此方法与 appendReplacement
结合使用,可在 Java 中实现基于正则表达式的强大文本操作,从简单的替换到复杂的模板处理。
作者
列出所有Java教程。