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

Lesezeit: 8 Minuten

Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal
Yossale

Ich muss viele verschiedene Teilzeichenfolgen 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?

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 im Voraus Zeit zum Kompilieren, daher ist es nicht effizient wenn Ihre Eingabe sehr gering ist oder sich Ihr Suchmuster häufig ändert).

Nachfolgend 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 (wenn Ihr regulärer Ausdruck jedoch komplex ist oder ein Backtracking beinhaltet, müssten Sie immer noch einen Benchmark durchführen, um dies zu bestätigen!)

  • Ja, muss jedoch für die Anzahl der Iterationen bewertet werden.

    – techzen

    25. August ’09 um 9:00 Uhr

  • Ich denke, Sie sollten Sonderzeichen in jedem Token entkommen, bevor Sie dies tun "%(" + StringUtils.join(tokens.keySet(), "|") + ")%";

    – Entwickler Marius Žilėnas

    19. März 15 um 8:59 Uhr


  • Beachten Sie, dass Sie StringBuilder für etwas mehr Geschwindigkeit verwenden können. StringBuilder ist nicht synchronisiert. edit whoops funktioniert allerdings nur mit java 9

    – Tinus Tate

    26. April 18 um 18:34 Uhr


  • Zukünftiger Leser: Für Regex wird “(” und “)” die zu durchsuchende Gruppe einschließen. Das “%” zählt im Text als Literal. Wenn Ihre Begriffe nicht mit “%” beginnen UND enden, werden sie nicht gefunden. Passen Sie also Präfixe und Suffixe auf beiden Teilen (Text + Code) an.

    – Linuxunil

    24. Mai 19 um 17:53 Uhr

Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal
David Jarvis

Algorithmus

Eine der effizientesten Möglichkeiten zum Ersetzen übereinstimmender Zeichenfolgen (ohne reguläre Ausdrücke) ist die Verwendung von Aho-Corasick-Algorithmus mit einem Darsteller Versuch (ausgesprochen “versuchen”), schnell hashen Algorithmus und effizient Sammlungen Implementierung.

Einfacher Code

Eine einfache Lösung nutzt Apache StringUtils.replaceEach folgendermaßen:

  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 bei großen Texten.

Schneller Code

Bors Implementierung des Aho-Corasick-Algorithmus führt etwas mehr Komplexität ein, die zu einem Implementierungsdetail wird, indem eine Fassade mit derselben Methodensignatur verwendet 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 erstellt zufälligNumerisch folgendermaßen:

  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 MATCHES_DIVISOR bestimmt die 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 zum Ersetzen.

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

Kein Wettbewerb.

10.000 : 1.000

Verwenden von 10.000 Zeichen und 1.000 übereinstimmenden Zeichenfolgen zum Ersetzen von:

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

Die Spaltung schließt sich.

1.000 : 10

Verwenden von 1.000 Zeichen und 10 übereinstimmenden Zeichenfolgen zum Ersetzen von:

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

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

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

Implementierungen

Erwägen Sie den Vergleich anderer Implementierungen für Text, der länger als 1 MB ist, einschließlich:

Papiere

Papiere und Informationen zum Algorithmus:

  • Kudos 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 (der JIT kann manchmal Optimierungen zaubern).

    – Tunaki

    28. November 16 um 18:22 Uhr


  • Entfernen Sie builder.onlyWholeWords() und es funktioniert ähnlich wie das Ersetzen von Zeichenfolgen.

    – Ondrej Sotolar

    16. September 19 um 13:35 Uhr

  • Vielen Dank für diese hervorragende Antwort. Das ist auf jeden Fall sehr hilfreich! Ich wollte nur anmerken, dass man, um die beiden Ansätze zu vergleichen und auch ein aussagekräftigeres Beispiel zu geben, den Trie nur einmal im zweiten Ansatz erstellen und auf viele verschiedene Eingabezeichenfolgen anwenden sollte. Ich denke, das ist der Hauptvorteil des Zugriffs auf Trie gegenüber StringUtils: Sie erstellen es nur einmal. Trotzdem vielen Dank für diese Antwort. Es teilt sehr gut die Methodik zur Umsetzung des zweiten Ansatzes

    – Vic Seedoubleyew

    5. März 20 um 15:42 Uhr

1644316149 517 Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal
Bikram

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-Bananen-Frucht-

  • Genau was ich brauchte mein Freund 🙂

    – GOXR3PLUS

    22. Januar 19 um 13:25 Uhr

  • input.replaceAll(“string1|string2|string3″,”replacementString1|replacementString2|replacementString3”) wäre großartig

    – yılmaz

    4. Oktober 21 um 0:15 Uhr


  • Beste Option von allen oben!

    – Jaco-Ben Vosloo

    1. Februar um 16:08 Uhr

Wenn Sie einen String viele Male ändern, ist es normalerweise effizienter, einen StringBuilder zu verwenden (aber messen Sie Ihre Leistung, um es 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 änderbar, das heißt, er kann beliebig verändert werden.

Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal
Brian Agnew

StringBuilder führt das Ersetzen effizienter durch, da sein Zeichen-Array-Puffer auf eine erforderliche Länge angegeben werden kann.StringBuilder ist für mehr als nur Anhängen ausgelegt!

Die eigentliche Frage ist natürlich, ob dies eine zu weitreichende Optimierung ist? Die JVM ist sehr gut im Umgang mit der Erstellung mehrerer Objekte und der anschließenden Garbage Collection, und wie bei allen Optimierungsfragen lautet meine erste Frage, ob Sie dies gemessen und festgestellt haben, dass es sich um ein Problem handelt.

1644316150 561 Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal
max

Überprüfen Sie dies:

String.format(str,STR[])

Zum Beispiel:

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

1644316150 804 Java Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal
Gelin Luo

Rythm, eine Java-Template-Engine, die jetzt mit einer neuen Funktion namens ” String-Interpolationsmodus was es Ihnen ermöglicht, Folgendes zu tun:

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

Der obige Fall zeigt, dass Sie Argumente nach Position an die Vorlage übergeben können. Rythm erlaubt Ihnen auch, Argumente nach Namen zu ü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 2- bis 3-mal schneller als String.format und die Geschwindigkeit, da es die Vorlage in Java-Bytecode kompiliert, ist die Laufzeitleistung sehr nahe an der Verkettung mit StringBuilder.

Verbindungen:

  • Dies ist eine sehr, sehr alte Funktion, die mit zahlreichen Templating-Sprachen wie Velocity und sogar JSP verfügbar ist. Es beantwortet auch nicht die Frage, für die die Suchzeichenfolgen nicht in einem vordefinierten Format vorliegen müssen.

    – Angsuman Chakraborty

    10. August 16 um 10:31 Uhr


  • Interessant, die akzeptierte Antwort liefert ein Beispiel: "%cat% really needs some %beverage%."; ist das nicht % getrenntes Token ein vordefiniertes Format? Ihr erster Punkt ist noch lustiger, JDK bietet viele “alte Fähigkeiten”, einige davon beginnen mit den 90er Jahren, warum machen sich die Leute die Mühe, sie zu verwenden? Ihre Kommentare und Ihr Downvoting machen keinen wirklichen Sinn

    – Gelin Luo

    11. August 16 um 08:06 Uhr

  • Was bringt es, die Rythm-Template-Engine einzuführen, wenn es bereits viele bereits existierende Template-Engines gibt, die weit verbreitet sind, wie zum Beispiel Velocity oder Freemarker? Warum auch ein weiteres Produkt einführen, wenn die Java-Kernfunktionalitäten mehr als ausreichen. Ich bezweifle Ihre Aussage zur Leistung sehr, da Pattern auch kompiliert werden kann. Würde gerne ein paar echte Zahlen sehen.

    – Angsuman Chakraborty

    12. August 16 um 5:32 Uhr

  • Grün, Sie verfehlen den Punkt. Der Fragesteller möchte beliebige Zeichenfolgen ersetzen, während Ihre Lösung nur Zeichenfolgen im vordefinierten Format wie vorangestelltes @ ersetzen wird. Ja, das Beispiel verwendet %, aber nur als Beispiel, nicht als einschränkenden Faktor. Sie beantworten also nicht die Frage und damit den negativen Punkt.

    – Angsuman Chakraborty

    12. August 16 um 5:34 Uhr


.

821510cookie-checkJava Ersetzen mehrerer verschiedener Teilzeichenfolgen in einer Zeichenfolge auf einmal (oder auf die effizienteste Weise)

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy