ZetCode

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 的基本用法

此示例演示了 appendTailappendReplacement 的基本用法。我们将把字符串中的所有数字替换成它们的单词等价物,同时保留其余文本。

AppendTailBasic.java
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 进行多次替换

此示例展示了如何在文本中执行多次替换,同时保持原始结构。我们将替换财务报表中的日期和货币值。

MultipleReplacements.java
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 来维护完整的输出。

SelectiveReplacement.java
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 标签,同时保留文本内容。

HtmlTagRemoval.java
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 如何处理完整的转换。

MarkdownToHtml.java
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。每个转换都使用 appendReplacementappendTail

中间结果在转换之间传递,appendTail 确保在步骤之间没有内容丢失。这演示了如何使用这些方法构建复杂的文本处理器。

在模板处理中使用 appendTail

此示例展示了如何在模板处理中使用 appendTail。我们将用实际值替换模板中的占位符,同时保留模板结构。

TemplateProcessor.java
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 与替代方法的性能影响。我们将比较大型文本处理的字符串构建技术。

PerformanceComparison.java
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())));
    }
}

此示例比较了对大型输入字符串使用 appendReplacementappendTailreplaceAll 的性能。输入包含许多数字,我们用 "NUM" 作为前缀。 appendTail 方法允许自定义处理每个匹配项,但对于简单的替换,可能比 replaceAll 慢。

时序结果显示了灵活性和性能之间的权衡。对于复杂的转换,使用 appendReplacementappendTail 更通用,而 replaceAll 针对简单的替换进行了优化。开发人员应根据其特定需求进行选择。

来源

Java Matcher.appendTail 文档

在本文中,我们通过实际示例深入探讨了 Matcher.appendTail 方法。此方法与 appendReplacement 结合使用,可在 Java 中实现基于正则表达式的强大文本操作,从简单的替换到复杂的模板处理。

作者

我的名字是 Jan Bodnar,我是一位经验丰富的程序员,在这一领域拥有多年的经验。我从 2007 年开始撰写编程文章,至今已撰写了 1,400 多篇文章和 8 本电子书。凭借超过 8 年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程