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教程。