Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java KMP substring search: reused prefix information and UTF-16 indices

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

Knuth–Morris–Pratt search uses a pattern-prefix fallback array so a mismatch can reuse earlier comparisons instead of restarting at the next text position.

Java 8+. This is a complete program using JDK classes.

Keep the meaning of each index

The prefix value at position i records the length of a proper pattern prefix that is also a suffix ending at i. On a mismatch, that value tells the search how much of its matched pattern can still be useful.

The text index never moves backward. The pattern index may fall back several times, but each successful advance creates only a bounded amount of later fallback work. That accounting gives the combined linear bound rather than merely hoping that typical inputs have few repeated prefixes.

This implementation compares Java char values and returns a UTF-16 index like ordinary String indexing. It does not normalize text or treat user-perceived characters as matching units. The empty pattern returns zero by the method contract; an absent nonempty pattern returns minus one.

Working program

Java
public class ManifestPatternSearch {
    static int[] prefixes(String pattern) {
        int[] fallback = new int[pattern.length()]; int matched=0;
        for (int i=1; i<pattern.length(); i++) {
            while (matched>0 && pattern.charAt(i)!=pattern.charAt(matched)) matched=fallback[matched-1];
            if (pattern.charAt(i)==pattern.charAt(matched)) matched++;
            fallback[i]=matched;
        }
        return fallback;
    }
    static int find(String text, String pattern) {
        java.util.Objects.requireNonNull(text); java.util.Objects.requireNonNull(pattern);
        if (pattern.isEmpty()) return 0;
        int[] fallback=prefixes(pattern); int matched=0;
        for (int i=0; i<text.length(); i++) {
            while (matched>0 && text.charAt(i)!=pattern.charAt(matched)) matched=fallback[matched-1];
            if (text.charAt(i)==pattern.charAt(matched)) matched++;
            if (matched==pattern.length()) return i-pattern.length()+1;
        }
        return -1;
    }
    public static void main(String[] args) {
        System.out.println(find("PKPKPKOK", "PKOK"));
        System.out.println(find("dispatch", "lost"));
        System.out.println(find("dispatch", ""));
    }
}

Output

Output
4
-1
0

Costs and boundaries

For text length n and pattern length m, preprocessing and search use O(n+m) comparisons and O(m) auxiliary storage. Null inputs are not accepted; the implementation will fail rather than interpret null as an empty string. Repeated searches for one pattern can reuse its preprocessed state in a separate immutable matcher.

Common Mistakes

  • The prefix must be proper; the whole prefix is not its own fallback.
  • A returned index is measured in UTF-16 units.
  • Resetting to zero on every mismatch loses the fallback information.

Read next

Text equality policy, Pattern grammar matching.

Extend the tested workflow

Continue with Java Z algorithm: find overlapping pattern matches in linear time.

java
kmp-search
Storage details