ZetCode

Java Matcher.start 方法

上次修改时间:2025 年 4 月 20 日

Java 中的 Matcher.start 方法返回匹配器找到的先前匹配项的起始索引。它是 java.util.regex 包的一部分,并与 PatternMatcher 类一起使用。

当您需要知道输入字符串中匹配模式的确切位置时,此方法至关重要。它有助于文本处理任务,如解析、搜索和文本操作。该方法有多个重载版本。

Matcher.start 方法概述

start 方法有三种形式:startstart(int group)start(String name)。基本版本返回整个匹配项的起始索引。组版本返回特定捕获组的起始索引。

在调用 start 之前,必须先调用匹配操作,如 findmatches。否则,它会抛出 IllegalStateException

基本的 Matcher.start 用法

此示例演示了 start 的最简单用法,用于查找匹配模式的位置。我们将在字符串中搜索单词 "Java",并获取它的起始位置。

MatcherStartBasic.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartBasic {

    public static void main(String[] args) {
        String input = "Learning Java programming is fun!";
        String regex = "Java";
        
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        
        if (matcher.find()) {
            int startPosition = matcher.start();
            System.out.println("Pattern '" + regex + "' found at position: " + startPosition);
            System.out.println("Matched text: '" + input.substring(startPosition, matcher.end()) + "'");
        } else {
            System.out.println("Pattern not found");
        }
    }
}

在此示例中,我们编译一个简单的模式,并为我们的输入字符串创建一个匹配器。find 方法定位 "Java" 的第一次出现。

当找到匹配项时,start 返回匹配项开始的从零开始的索引。我们还使用 substring 以及 startend 位置来显示匹配的文本。

带有组的 Matcher.start

此示例展示了如何使用 start(int group) 来获取正则表达式模式中特定捕获组的起始位置。我们将解析一个日期字符串并提取组件位置。

MatcherStartGroups.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartGroups {

    public static void main(String[] args) {
        String input = "Date: 2025-04-20";
        String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
        
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        
        if (matcher.find()) {
            System.out.println("Full match starts at: " + matcher.start());
            System.out.println("Year starts at: " + matcher.start(1));
            System.out.println("Month starts at: " + matcher.start(2));
            System.out.println("Day starts at: " + matcher.start(3));
            
            System.out.println("\nMatched components:");
            System.out.println("Year: " + matcher.group(1));
            System.out.println("Month: " + matcher.group(2));
            System.out.println("Day: " + matcher.group(3));
        }
    }
}

这里我们定义了一个模式,其中包含三个捕获组,分别用于年、月和日。找到匹配项后,我们使用 start(group) 来获取每个组件的起始位置。组 0 指的是整个匹配项,而组 1-3 是我们定义的捕获组。

当您需要匹配的文本及其在输入字符串中的确切位置时,此技术非常有用。

带有命名组的 Matcher.start

Java 支持正则表达式模式中的命名捕获组。此示例演示了如何将 start(String name) 与命名组一起使用,以获得更具可读性的代码。我们将解析一个带有命名组件的日志条目。

MatcherStartNamedGroups.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartNamedGroups {

    public static void main(String[] args) {
        String input = "[ERROR] 2025-04-20 14:30:45 - Connection timeout";
        String regex = "\\[(?<level>\\w+)\\] (?<date>\\d{4}-\\d{2}-\\d{2}) (?<time>\\d{2}:\\d{2}:\\d{2}) - (?<message>.*)";
        
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        
        if (matcher.find()) {
            System.out.println("Log level starts at: " + matcher.start("level"));
            System.out.println("Date starts at: " + matcher.start("date"));
            System.out.println("Time starts at: " + matcher.start("time"));
            System.out.println("Message starts at: " + matcher.start("message"));
            
            System.out.println("\nLog components:");
            System.out.println("Level: " + matcher.group("level"));
            System.out.println("Date: " + matcher.group("date"));
            System.out.println("Time: " + matcher.group("time"));
            System.out.println("Message: " + matcher.group("message"));
        }
    }
}

此示例使用命名组 (?<name>pattern) 来捕获日志组件。start(String name) 方法检索每个命名组的起始位置。命名组通过使用描述性名称而不是数字索引,使代码更易于维护。

该模式匹配包含级别、日期、时间和消息组件的日志条目。我们提取每个组件的位置和值。

带有 start() 的多个匹配项

此示例展示了如何在字符串中使用 start 与多个匹配项。我们将在文本中查找某个单词的所有出现及其位置。

MatcherStartMultiple.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartMultiple {

    public static void main(String[] args) {
        String input = "The quick brown fox jumps over the lazy dog. The quick fox.";
        String regex = "\\bfox\\b";
        
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        
        int matchCount = 0;
        while (matcher.find()) {
            matchCount++;
            int start = matcher.start();
            System.out.println("Match " + matchCount + ":");
            System.out.println("  Starts at: " + start);
            System.out.println("  Ends at: " + matcher.end());
            System.out.println("  Text: '" + input.substring(start, matcher.end()) + "'");
            System.out.println("  Context: '" + getContext(input, start, matcher.end()) + "'");
        }
        
        System.out.println("\nTotal matches found: " + matchCount);
    }
    
    private static String getContext(String input, int start, int end) {
        int contextStart = Math.max(0, start - 5);
        int contextEnd = Math.min(input.length(), end + 5);
        return input.substring(contextStart, contextEnd);
    }
}

此代码查找输入字符串中 "fox" 的所有完整单词出现。对于每个匹配项,我们使用 start 获取其位置,并显示匹配项周围的上下文信息。getContext 方法显示每个匹配项周围的文本,以便更好地可视化。

该示例演示了如何在循环中处理多个匹配项,其中 find 每次都前进到下一个匹配项。

使用 start() 的错误处理

此示例演示了在使用 start 时的正确错误处理。我们将展示常见错误以及在使用匹配器位置时如何避免这些错误。

MatcherStartErrors.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartErrors {

    public static void main(String[] args) {
        String input = "Sample text with numbers 123 and 456";
        String regex = "\\d+";
        
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        
        // Error: Calling start() before find()
        try {
            System.out.println("Attempt to call start() before find():");
            System.out.println(matcher.start());
        } catch (IllegalStateException e) {
            System.out.println("  Error: " + e.getMessage());
        }
        
        // Proper usage
        if (matcher.find()) {
            System.out.println("\nFirst number starts at: " + matcher.start());
            
            // Error: Calling start() for non-existent group
            try {
                System.out.println("\nAttempt to get non-existent group:");
                System.out.println(matcher.start(2));
            } catch (IndexOutOfBoundsException e) {
                System.out.println("  Error: " + e.getMessage());
            }
        }
        
        // Error: Calling start() after matches()
        matcher.reset();
        matcher.matches(); // matches() resets the matcher
        try {
            System.out.println("\nAttempt to call start() after matches():");
            System.out.println(matcher.start());
        } catch (IllegalStateException e) {
            System.out.println("  Error: " + e.getMessage());
        }
    }
}

此示例重点介绍了使用 start 时的三个常见错误场景:在任何匹配操作之前调用它,请求不存在的组,以及在 matches 之后调用它,而没有新的匹配尝试。

每种情况都包含在一个 try-catch 块中,以演示抛出的特定异常。正确的错误处理使您的正则表达式代码更健壮且更易于维护。

文本处理中的 Matcher.start

这个实际示例展示了如何在实际文本处理中使用 start。我们将提取并突出显示文档中的所有电子邮件地址。

MatcherStartEmail.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartEmail {

    public static void main(String[] args) {
        String document = "Contact us at support@example.com or sales@company.com.\n" +
                        "For help, email help@service.org. Invalid emails: user@, @domain.com";
        
        String emailRegex = "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b";
        
        Pattern pattern = Pattern.compile(emailRegex);
        Matcher matcher = pattern.matcher(document);
        
        System.out.println("Original document:\n" + document);
        System.out.println("\nEmail addresses found:");
        
        StringBuilder highlighted = new StringBuilder(document);
        int offset = 0;
        
        while (matcher.find()) {
            int start = matcher.start();
            int end = matcher.end();
            String email = matcher.group();
            
            System.out.println("- " + email + " (position " + start + "-" + (end-1) + ")");
            
            // Highlight the email in the document
            highlighted.insert(start + offset, "[");
            highlighted.insert(end + offset + 1, "]");
            offset += 2; // Account for added brackets
        }
        
        System.out.println("\nDocument with highlighted emails:");
        System.out.println(highlighted.toString());
    }
}

此示例使用综合正则表达式模式扫描文档中有效的电子邮件地址。对于找到的每个电子邮件,我们使用 startend 获取其确切位置,然后通过在其周围添加括号来在原始文本中突出显示它。

该代码演示了 start 如何用于文本标记和分析任务。offset 变量跟踪由于我们对字符串的修改而导致的位置变化。

高级 Matcher.start 用法

此最终示例展示了一个高级用例,将 start 与其他 Matcher 方法结合使用。我们将解析一个包含多个模式和嵌套组的复杂字符串。

MatcherStartAdvanced.java
package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MatcherStartAdvanced {

    public static void main(String[] args) {
        String config = "server { host: 192.168.1.1; port: 8080; } " +
                      "client { timeout: 30; retries: 3; }";
        
        String serverBlockRegex = "server\\s*\\{ (?<serverContent>.*?) \\}";
        String clientBlockRegex = "client\\s*\\{ (?<clientContent>.*?) \\}";
        String propertyRegex = "(?<key>\\w+)\\s*:\\s*(?<value>[^;]+)";
        
        // Find server block
        Pattern serverPattern = Pattern.compile(serverBlockRegex);
        Matcher serverMatcher = serverPattern.matcher(config);
        
        if (serverMatcher.find()) {
            System.out.println("Server block found at position " + 
                serverMatcher.start() + "-" + (serverMatcher.end()-1));
            
            String serverContent = serverMatcher.group("serverContent");
            
            // Parse properties within server block
            Pattern propertyPattern = Pattern.compile(propertyRegex);
            Matcher propertyMatcher = propertyPattern.matcher(serverContent);
            
            System.out.println("Server block properties:");
            while (propertyMatcher.find()) {
                System.out.println("  Property '" + propertyMatcher.group("key") + 
                    "' starts at: " + (serverMatcher.start() + propertyMatcher.start()) + 
                    ", value: " + propertyMatcher.group("value"));
            }
        }
        
        // Find client block
        Pattern clientPattern = Pattern.compile(clientBlockRegex);
        Matcher clientMatcher = clientPattern.matcher(config);
        
        if (clientMatcher.find()) {
            System.out.println("\nClient block found at position " + 
                clientMatcher.start() + "-" + (clientMatcher.end()-1));
                
            String clientContent = clientMatcher.group("clientContent");
            
            // Parse properties within client block
            Matcher propertyMatcher = Pattern.compile(propertyRegex).matcher(clientContent);
            
            System.out.println("Client block properties:");
            while (propertyMatcher.find()) {
                System.out.println("  Property '" + propertyMatcher.group("key") + 
                    "' starts at: " + (clientMatcher.start() + propertyMatcher.start()) + 
                    ", value: " + propertyMatcher.group("value"));
            }
        }
    }
}

这个高级示例演示了如何使用 start 来跟踪嵌套模式匹配中的位置。我们解析一个包含服务器和客户端块的配置字符串,每个块都包含键值属性。外部匹配器查找块,内部匹配器解析每个块中的属性。

start 方法用于通过将块的起始位置与每个属性在块内容中的相对位置相结合来计算每个属性在原始字符串中的绝对位置。这对于调试或记录复杂解析任务中的确切位置特别有用。

来源

Java Matcher.start 文档

在本文中,我们通过实际示例深入探讨了 Matcher.start 方法。这种基本方法可以精确跟踪 Java 正则表达式应用程序中的匹配位置,从基本模式匹配到复杂的文本处理任务。

作者

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

列出所有Java教程