Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge gleichzeitig (oder auf die effizienteste Weise)

97

Ich muss viele verschiedene Unterzeichenfolgen in einer Zeichenfolge auf die effizienteste Weise ersetzen. Gibt es einen anderen Weg als den Brute-Force-Weg, um jedes Feld mit string.replace zu ersetzen?

Yossale
quelle

Antworten:

101

Wenn die Zeichenfolge, mit der Sie arbeiten, sehr lang ist oder Sie mit vielen Zeichenfolgen arbeiten, kann es sich lohnen, einen java.util.regex.Matcher zu verwenden (dies erfordert Zeit im Voraus zum Kompilieren, sodass es nicht effizient ist wenn Ihre Eingabe sehr klein ist oder sich Ihr Suchmuster häufig ändert).

Unten finden Sie ein vollständiges Beispiel, das auf einer Liste von Token basiert, die einer Karte entnommen wurden. (Verwendet StringUtils von Apache Commons Lang).

Map<String,String> tokens = new HashMap<String,String>();
tokens.put("cat", "Garfield");
tokens.put("beverage", "coffee");

String template = "%cat% really needs some %beverage%.";

// Create pattern of the format "%(cat|beverage)%"
String patternString = "%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(template);

StringBuffer sb = new StringBuffer();
while(matcher.find()) {
    matcher.appendReplacement(sb, tokens.get(matcher.group(1)));
}
matcher.appendTail(sb);

System.out.println(sb.toString());

Sobald der reguläre Ausdruck kompiliert ist, ist das Scannen der Eingabezeichenfolge im Allgemeinen sehr schnell (obwohl Sie, wenn Ihr regulärer Ausdruck komplex ist oder ein Backtracking beinhaltet, immer noch einen Benchmark durchführen müssen, um dies zu bestätigen!).

Todd Owen
quelle
1
Ja, muss jedoch für die Anzahl der Iterationen verglichen werden.
Techzen
5
Ich denke, Sie sollten Sonderzeichen in jedem Token entkommen, bevor Sie dies tun"%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Entwickler Marius Žilėnas
Beachten Sie, dass man StringBuilder für etwas mehr Geschwindigkeit verwenden kann. StringBuilder ist nicht synchronisiert. edit whoops funktioniert allerdings nur mit java 9
Tinus Tate
3
Zukünftiger Leser: Für Regex schließen "(" und ")" die zu suchende Gruppe ein. Das "%" zählt als Literal im Text. Wenn Ihre Begriffe nicht mit "%" beginnen UND enden, werden sie nicht gefunden. Passen Sie also Präfixe und Suffixe an beiden Teilen an (Text + Code).
Linuxunil
65

Algorithmus

Eine der effizientesten Möglichkeiten, übereinstimmende Zeichenfolgen (ohne reguläre Ausdrücke) zu ersetzen, besteht darin, den Aho-Corasick-Algorithmus durch einen performanten Trie (ausgesprochen "try"), einen schnellen Hashing- Algorithmus und eine effiziente Implementierung von Sammlungen zu verwenden .

Einfacher Code

Eine einfache Lösung nutzt Apache StringUtils.replaceEachwie folgt:

  private String testStringUtils(
    final String text, final Map<String, String> definitions ) {
    final String[] keys = keys( definitions );
    final String[] values = values( definitions );

    return StringUtils.replaceEach( text, keys, values );
  }

Dies verlangsamt große Texte.

Schneller Code

Bors Implementierung des Aho-Corasick-Algorithmus führt zu einer etwas höheren Komplexität, die durch die Verwendung einer Fassade mit derselben Methodensignatur zu einem Implementierungsdetail wird:

  private String testBorAhoCorasick(
    final String text, final Map<String, String> definitions ) {
    // Create a buffer sufficiently large that re-allocations are minimized.
    final StringBuilder sb = new StringBuilder( text.length() << 1 );

    final TrieBuilder builder = Trie.builder();
    builder.onlyWholeWords();
    builder.removeOverlaps();

    final String[] keys = keys( definitions );

    for( final String key : keys ) {
      builder.addKeyword( key );
    }

    final Trie trie = builder.build();
    final Collection<Emit> emits = trie.parseText( text );

    int prevIndex = 0;

    for( final Emit emit : emits ) {
      final int matchIndex = emit.getStart();

      sb.append( text.substring( prevIndex, matchIndex ) );
      sb.append( definitions.get( emit.getKeyword() ) );
      prevIndex = emit.getEnd() + 1;
    }

    // Add the remainder of the string (contains no more matches).
    sb.append( text.substring( prevIndex ) );

    return sb.toString();
  }

Benchmarks

Für die Benchmarks wurde der Puffer mit randomNumeric wie folgt erstellt:

  private final static int TEXT_SIZE = 1000;
  private final static int MATCHES_DIVISOR = 10;

  private final static StringBuilder SOURCE
    = new StringBuilder( randomNumeric( TEXT_SIZE ) );

Wo bestimmt MATCHES_DIVISORdie Anzahl der zu injizierenden Variablen:

  private void injectVariables( final Map<String, String> definitions ) {
    for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) {
      final int r = current().nextInt( 1, SOURCE.length() );
      SOURCE.insert( r, randomKey( definitions ) );
    }
  }

Der Benchmark-Code selbst ( JMH schien übertrieben):

long duration = System.nanoTime();
final String result = testBorAhoCorasick( text, definitions );
duration = System.nanoTime() - duration;
System.out.println( elapsed( duration ) );

1.000.000: 1.000

Ein einfacher Mikro-Benchmark mit 1.000.000 Zeichen und 1.000 zufällig platzierten Zeichenfolgen, die ersetzt werden müssen.

  • testStringUtils: 25 Sekunden, 25533 Millis
  • testBorAhoCorasick: 0 Sekunden, 68 Millis

Kein Wettbewerb.

10.000: 1.000

Verwenden Sie 10.000 Zeichen und 1.000 übereinstimmende Zeichenfolgen, um Folgendes zu ersetzen:

  • testStringUtils: 1 Sekunde, 1402 Millis
  • testBorAhoCorasick: 0 Sekunden, 37 Millis

Die Kluft schließt sich.

1.000: 10

Verwenden Sie 1.000 Zeichen und 10 übereinstimmende Zeichenfolgen, um Folgendes zu ersetzen:

  • testStringUtils: 0 Sekunden, 7 Millis
  • testBorAhoCorasick: 0 Sekunden, 19 Millis

Bei kurzen Saiten übertrifft der Aufwand für die Einrichtung von Aho-Corasick den Brute-Force-Ansatz um StringUtils.replaceEach.

Ein hybrider Ansatz basierend auf der Textlänge ist möglich, um das Beste aus beiden Implementierungen herauszuholen.

Implementierungen

Vergleichen Sie andere Implementierungen für Text, der länger als 1 MB ist, einschließlich:

Papiere

Artikel und Informationen zum Algorithmus:

Dave Jarvis
quelle
5
Ein großes Lob für die Aktualisierung dieser Frage mit neuen wertvollen Informationen, das ist sehr schön. Ich denke, ein JMH-Benchmark ist immer noch angemessen, zumindest für vernünftige Werte wie 10.000: 1.000 und 1.000: 10 (die JIT kann manchmal magische Optimierungen vornehmen).
Tunaki
Entfernen Sie builder.onlyWholeWords () und es funktioniert ähnlich wie beim Ersetzen von Zeichenfolgen.
Ondrej Sotolar
Vielen Dank für diese hervorragende Antwort. Das ist auf jeden Fall sehr hilfreich! Ich wollte nur kommentieren, dass man, um die beiden Ansätze zu vergleichen und auch ein aussagekräftigeres Beispiel zu geben, den Trie im zweiten Ansatz nur einmal erstellen und auf viele verschiedene Eingabezeichenfolgen anwenden sollte. Ich denke, dies ist der Hauptvorteil für den Zugriff auf Trie gegenüber StringUtils: Sie erstellen es nur einmal. Trotzdem vielen Dank für diese Antwort. Es teilt sehr gut die Methodik zur Implementierung des zweiten Ansatzes
Vic Seedoubleyew
Ein ausgezeichneter Punkt, @VicSeedoubleyew. Möchten Sie die Antwort aktualisieren?
Dave Jarvis
8

Das hat bei mir funktioniert:

String result = input.replaceAll("string1|string2|string3","replacementString");

Beispiel:

String input = "applemangobananaarefruits";
String result = input.replaceAll("mango|are|ts","-");
System.out.println(result);

Ausgabe: Apfel-Banane-Frucht-

Bikram
quelle
Genau das, was ich brauchte, mein Freund :)
GOXR3PLUS
7

Wenn Sie einen String mehrmals ändern, ist es normalerweise effizienter, einen StringBuilder zu verwenden (aber messen Sie Ihre Leistung, um dies herauszufinden) :

String str = "The rain in Spain falls mainly on the plain";
StringBuilder sb = new StringBuilder(str);
// do your replacing in sb - although you'll find this trickier than simply using String
String newStr = sb.toString();

Jedes Mal, wenn Sie einen String ersetzen, wird ein neues String-Objekt erstellt, da Strings unveränderlich sind. StringBuilder ist veränderbar, dh es kann beliebig geändert werden.

Steve McLeod
quelle
Ich fürchte, es hilft nicht. Immer wenn der Austausch in der Länge vom Original abweicht, müssen Sie etwas verschieben, was teurer sein kann, als die Saite neu zu bauen. Oder fehlt mir etwas?
Maaartinus
4

StringBuilderwird das Ersetzen effizienter durchführen, da sein Zeichenarray-Puffer auf eine erforderliche Länge angegeben werden kann. StringBuilderist mehr als nur zum Anhängen gedacht!

Die eigentliche Frage ist natürlich, ob dies eine zu weit gehende Optimierung ist. Die JVM kann sehr gut mit der Erstellung mehrerer Objekte und der anschließenden Speicherbereinigung umgehen. Wie bei allen Optimierungsfragen ist meine erste Frage, ob Sie dies gemessen und festgestellt haben, dass es sich um ein Problem handelt.

Brian Agnew
quelle
2

Wie wäre es mit der replaceAll () -Methode?

Avi
quelle
4
In einem regulären Ausdruck können viele verschiedene Teilzeichenfolgen verarbeitet werden (/substring1|substring2|.../). Es hängt alles davon ab, welche Art von Ersatz das OP versucht.
Avi
4
Das OP sucht nach etwas Effizienterem alsstr.replaceAll(search1, replace1).replaceAll(search2, replace2).replaceAll(search3, replace3).replaceAll(search4, replace4)
Kip
2

Rythm ist eine Java-Template-Engine, die jetzt mit einer neuen Funktion namens String-Interpolationsmodus veröffentlicht wurde, mit der Sie Folgendes tun können:

String result = Rythm.render("@name is inviting you", "Diana");

Der obige Fall zeigt, dass Sie Argumente nach Position an die Vorlage übergeben können. Mit Rythm können Sie Argumente auch nach Namen übergeben:

Map<String, Object> args = new HashMap<String, Object>();
args.put("title", "Mr.");
args.put("name", "John");
String result = Rythm.render("Hello @title @name", args);

Hinweis Rythm ist SEHR SCHNELL, etwa zwei- bis dreimal schneller als String.format und Geschwindigkeit, da die Vorlage in Java-Bytecode kompiliert wird und die Laufzeitleistung der Konzentration mit StringBuilder sehr nahe kommt.

Links:

Gelin Luo
quelle
Dies ist eine sehr sehr alte Funktion, die mit zahlreichen Vorlagensprachen wie Geschwindigkeit und sogar JSP verfügbar ist. Außerdem wird die Frage nicht beantwortet, bei der die Suchzeichenfolgen nicht in einem vordefinierten Format vorliegen müssen.
Angsuman Chakraborty
Interessanterweise liefert die akzeptierte Antwort ein Beispiel: Ist "%cat% really needs some %beverage%."; dieses %getrennte Token nicht ein vordefiniertes Format? Ihr erster Punkt ist noch lustiger, JDK bietet viele "alte Fähigkeiten", einige davon beginnen in den 90ern, warum machen sich die Leute die Mühe, sie zu benutzen? Ihre Kommentare und Abstimmungen machen keinen wirklichen Sinn
Gelin Luo
Was bringt es, die Rythm-Template-Engine einzuführen, wenn es bereits viele bereits vorhandene Template-Engines gibt, die häufig zum Starten wie Velocity oder Freemarker verwendet werden? Warum auch ein anderes Produkt einführen, wenn die Java-Kernfunktionen mehr als ausreichen? Ich bezweifle wirklich Ihre Aussage zur Leistung, da Pattern auch kompiliert werden kann. Würde gerne einige reelle Zahlen sehen.
Angsuman Chakraborty
Grün, Sie verpassen den Punkt. Der Fragesteller möchte beliebige Zeichenfolgen ersetzen, während Ihre Lösung nur Zeichenfolgen im vordefinierten Format wie @ vorangestellt ersetzt. Ja, das Beispiel verwendet%, aber nur als Beispiel, nicht als einschränkenden Faktor. Ihre Antwort beantwortet also nicht die Frage und damit den negativen Punkt.
Angsuman Chakraborty
2

Das Folgende basiert auf der Antwort von Todd Owen . Diese Lösung hat das Problem, dass unerwartete Ergebnisse erzielt werden können, wenn die Ersetzungen Zeichen enthalten, die in regulären Ausdrücken eine besondere Bedeutung haben. Ich wollte auch in der Lage sein, optional eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung durchzuführen. Folgendes habe ich mir ausgedacht:

/**
 * Performs simultaneous search/replace of multiple strings. Case Sensitive!
 */
public String replaceMultiple(String target, Map<String, String> replacements) {
  return replaceMultiple(target, replacements, true);
}

/**
 * Performs simultaneous search/replace of multiple strings.
 * 
 * @param target        string to perform replacements on.
 * @param replacements  map where key represents value to search for, and value represents replacem
 * @param caseSensitive whether or not the search is case-sensitive.
 * @return replaced string
 */
public String replaceMultiple(String target, Map<String, String> replacements, boolean caseSensitive) {
  if(target == null || "".equals(target) || replacements == null || replacements.size() == 0)
    return target;

  //if we are doing case-insensitive replacements, we need to make the map case-insensitive--make a new map with all-lower-case keys
  if(!caseSensitive) {
    Map<String, String> altReplacements = new HashMap<String, String>(replacements.size());
    for(String key : replacements.keySet())
      altReplacements.put(key.toLowerCase(), replacements.get(key));

    replacements = altReplacements;
  }

  StringBuilder patternString = new StringBuilder();
  if(!caseSensitive)
    patternString.append("(?i)");

  patternString.append('(');
  boolean first = true;
  for(String key : replacements.keySet()) {
    if(first)
      first = false;
    else
      patternString.append('|');

    patternString.append(Pattern.quote(key));
  }
  patternString.append(')');

  Pattern pattern = Pattern.compile(patternString.toString());
  Matcher matcher = pattern.matcher(target);

  StringBuffer res = new StringBuffer();
  while(matcher.find()) {
    String match = matcher.group(1);
    if(!caseSensitive)
      match = match.toLowerCase();
    matcher.appendReplacement(res, replacements.get(match));
  }
  matcher.appendTail(res);

  return res.toString();
}

Hier sind meine Unit-Testfälle:

@Test
public void replaceMultipleTest() {
  assertNull(ExtStringUtils.replaceMultiple(null, null));
  assertNull(ExtStringUtils.replaceMultiple(null, Collections.<String, String>emptyMap()));
  assertEquals("", ExtStringUtils.replaceMultiple("", null));
  assertEquals("", ExtStringUtils.replaceMultiple("", Collections.<String, String>emptyMap()));

  assertEquals("folks, we are not sane anymore. with me, i promise you, we will burn in flames", ExtStringUtils.replaceMultiple("folks, we are not winning anymore. with me, i promise you, we will win big league", makeMap("win big league", "burn in flames", "winning", "sane")));

  assertEquals("bcaacbbcaacb", ExtStringUtils.replaceMultiple("abccbaabccba", makeMap("a", "b", "b", "c", "c", "a")));
  assertEquals("bcaCBAbcCCBb", ExtStringUtils.replaceMultiple("abcCBAabCCBa", makeMap("a", "b", "b", "c", "c", "a")));
  assertEquals("bcaacbbcaacb", ExtStringUtils.replaceMultiple("abcCBAabCCBa", makeMap("a", "b", "b", "c", "c", "a"), false));

  assertEquals("c colon  backslash temp backslash  star  dot  star ", ExtStringUtils.replaceMultiple("c:\\temp\\*.*", makeMap(".", " dot ", ":", " colon ", "\\", " backslash ", "*", " star "), false));
}

private Map<String, String> makeMap(String ... vals) {
  Map<String, String> map = new HashMap<String, String>(vals.length / 2);
  for(int i = 1; i < vals.length; i+= 2)
    map.put(vals[i-1], vals[i]);
  return map;
}
Pennen
quelle
1
public String replace(String input, Map<String, String> pairs) {
  // Reverse lexic-order of keys is good enough for most cases,
  // as it puts longer words before their prefixes ("tool" before "too").
  // However, there are corner cases, which this algorithm doesn't handle
  // no matter what order of keys you choose, eg. it fails to match "edit"
  // before "bed" in "..bedit.." because "bed" appears first in the input,
  // but "edit" may be the desired longer match. Depends which you prefer.
  final Map<String, String> sorted = 
      new TreeMap<String, String>(Collections.reverseOrder());
  sorted.putAll(pairs);
  final String[] keys = sorted.keySet().toArray(new String[sorted.size()]);
  final String[] vals = sorted.values().toArray(new String[sorted.size()]);
  final int lo = 0, hi = input.length();
  final StringBuilder result = new StringBuilder();
  int s = lo;
  for (int i = s; i < hi; i++) {
    for (int p = 0; p < keys.length; p++) {
      if (input.regionMatches(i, keys[p], 0, keys[p].length())) {
        /* TODO: check for "edit", if this is "bed" in "..bedit.." case,
         * i.e. look ahead for all prioritized/longer keys starting within
         * the current match region; iff found, then ignore match ("bed")
         * and continue search (find "edit" later), else handle match. */
        // if (better-match-overlaps-right-ahead)
        //   continue;
        result.append(input, s, i).append(vals[p]);
        i += keys[p].length();
        s = i--;
      }
    }
  }
  if (s == lo) // no matches? no changes!
    return input;
  return result.append(input, s, hi).toString();
}
Robin479
quelle
1

Überprüfen Sie dies:

String.format(str,STR[])

Zum Beispiel:

String.format( "Put your %s where your %s is", "money", "mouth" );
Ali
quelle
0

Zusammenfassung: Implementierung von Daves Antwort in einer Klasse, um automatisch den effizientesten der beiden Algorithmen auszuwählen.

Dies ist eine vollständige Implementierung in einer Klasse, die auf der oben genannten hervorragenden Antwort von Dave Jarvis basiert . Die Klasse wählt automatisch zwischen den beiden verschiedenen bereitgestellten Algorithmen, um maximale Effizienz zu erzielen. (Diese Antwort ist für Personen gedacht, die nur schnell kopieren und einfügen möchten.)

ReplaceStrings-Klasse:

package somepackage

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.ahocorasick.trie.Emit;
import org.ahocorasick.trie.Trie;
import org.ahocorasick.trie.Trie.TrieBuilder;
import org.apache.commons.lang3.StringUtils;

/**
 * ReplaceStrings, This class is used to replace multiple strings in a section of text, with high
 * time efficiency. The chosen algorithms were adapted from: https://stackoverflow.com/a/40836618
 */
public final class ReplaceStrings {

    /**
     * replace, This replaces multiple strings in a section of text, according to the supplied
     * search and replace definitions. For maximum efficiency, this will automatically choose
     * between two possible replacement algorithms.
     *
     * Performance note: If it is known in advance that the source text is long, then this method
     * signature has a very small additional performance advantage over the other method signature.
     * (Although either method signature will still choose the best algorithm.)
     */
    public static String replace(
        final String sourceText, final Map<String, String> searchReplaceDefinitions) {
        final boolean useLongAlgorithm
            = (sourceText.length() > 1000 || searchReplaceDefinitions.size() > 25);
        if (useLongAlgorithm) {
            // No parameter adaptations are needed for the long algorithm.
            return replaceUsing_AhoCorasickAlgorithm(sourceText, searchReplaceDefinitions);
        } else {
            // Create search and replace arrays, which are needed by the short algorithm.
            final ArrayList<String> searchList = new ArrayList<>();
            final ArrayList<String> replaceList = new ArrayList<>();
            final Set<Map.Entry<String, String>> allEntries = searchReplaceDefinitions.entrySet();
            for (Map.Entry<String, String> entry : allEntries) {
                searchList.add(entry.getKey());
                replaceList.add(entry.getValue());
            }
            return replaceUsing_StringUtilsAlgorithm(sourceText, searchList, replaceList);
        }
    }

    /**
     * replace, This replaces multiple strings in a section of text, according to the supplied
     * search strings and replacement strings. For maximum efficiency, this will automatically
     * choose between two possible replacement algorithms.
     *
     * Performance note: If it is known in advance that the source text is short, then this method
     * signature has a very small additional performance advantage over the other method signature.
     * (Although either method signature will still choose the best algorithm.)
     */
    public static String replace(final String sourceText,
        final ArrayList<String> searchList, final ArrayList<String> replacementList) {
        if (searchList.size() != replacementList.size()) {
            throw new RuntimeException("ReplaceStrings.replace(), "
                + "The search list and the replacement list must be the same size.");
        }
        final boolean useLongAlgorithm = (sourceText.length() > 1000 || searchList.size() > 25);
        if (useLongAlgorithm) {
            // Create a definitions map, which is needed by the long algorithm.
            HashMap<String, String> definitions = new HashMap<>();
            final int searchListLength = searchList.size();
            for (int index = 0; index < searchListLength; ++index) {
                definitions.put(searchList.get(index), replacementList.get(index));
            }
            return replaceUsing_AhoCorasickAlgorithm(sourceText, definitions);
        } else {
            // No parameter adaptations are needed for the short algorithm.
            return replaceUsing_StringUtilsAlgorithm(sourceText, searchList, replacementList);
        }
    }

    /**
     * replaceUsing_StringUtilsAlgorithm, This is a string replacement algorithm that is most
     * efficient for sourceText under 1000 characters, and less than 25 search strings.
     */
    private static String replaceUsing_StringUtilsAlgorithm(final String sourceText,
        final ArrayList<String> searchList, final ArrayList<String> replacementList) {
        final String[] searchArray = searchList.toArray(new String[]{});
        final String[] replacementArray = replacementList.toArray(new String[]{});
        return StringUtils.replaceEach(sourceText, searchArray, replacementArray);
    }

    /**
     * replaceUsing_AhoCorasickAlgorithm, This is a string replacement algorithm that is most
     * efficient for sourceText over 1000 characters, or large lists of search strings.
     */
    private static String replaceUsing_AhoCorasickAlgorithm(final String sourceText,
        final Map<String, String> searchReplaceDefinitions) {
        // Create a buffer sufficiently large that re-allocations are minimized.
        final StringBuilder sb = new StringBuilder(sourceText.length() << 1);
        final TrieBuilder builder = Trie.builder();
        builder.onlyWholeWords();
        builder.ignoreOverlaps();
        for (final String key : searchReplaceDefinitions.keySet()) {
            builder.addKeyword(key);
        }
        final Trie trie = builder.build();
        final Collection<Emit> emits = trie.parseText(sourceText);
        int prevIndex = 0;
        for (final Emit emit : emits) {
            final int matchIndex = emit.getStart();

            sb.append(sourceText.substring(prevIndex, matchIndex));
            sb.append(searchReplaceDefinitions.get(emit.getKeyword()));
            prevIndex = emit.getEnd() + 1;
        }
        // Add the remainder of the string (contains no more matches).
        sb.append(sourceText.substring(prevIndex));
        return sb.toString();
    }

    /**
     * main, This contains some test and example code.
     */
    public static void main(String[] args) {
        String shortSource = "The quick brown fox jumped over something. ";
        StringBuilder longSourceBuilder = new StringBuilder();
        for (int i = 0; i < 50; ++i) {
            longSourceBuilder.append(shortSource);
        }
        String longSource = longSourceBuilder.toString();
        HashMap<String, String> searchReplaceMap = new HashMap<>();
        ArrayList<String> searchList = new ArrayList<>();
        ArrayList<String> replaceList = new ArrayList<>();
        searchReplaceMap.put("fox", "grasshopper");
        searchReplaceMap.put("something", "the mountain");
        searchList.add("fox");
        replaceList.add("grasshopper");
        searchList.add("something");
        replaceList.add("the mountain");
        String shortResultUsingArrays = replace(shortSource, searchList, replaceList);
        String shortResultUsingMap = replace(shortSource, searchReplaceMap);
        String longResultUsingArrays = replace(longSource, searchList, replaceList);
        String longResultUsingMap = replace(longSource, searchReplaceMap);
        System.out.println(shortResultUsingArrays);
        System.out.println("----------------------------------------------");
        System.out.println(shortResultUsingMap);
        System.out.println("----------------------------------------------");
        System.out.println(longResultUsingArrays);
        System.out.println("----------------------------------------------");
        System.out.println(longResultUsingMap);
        System.out.println("----------------------------------------------");
    }
}

Benötigte Maven-Abhängigkeiten:

(Fügen Sie diese bei Bedarf Ihrer POM-Datei hinzu.)

    <!-- Apache Commons utilities. Super commonly used utilities.
    https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.10</version>
    </dependency>

    <!-- ahocorasick, An algorithm used for efficient searching and 
    replacing of multiple strings.
    https://mvnrepository.com/artifact/org.ahocorasick/ahocorasick -->
    <dependency>
        <groupId>org.ahocorasick</groupId>
        <artifactId>ahocorasick</artifactId>
        <version>0.4.0</version>
    </dependency>
BlakeTNC
quelle