Java Bir dizedeki birden çok farklı alt dizeyi aynı anda (veya en verimli şekilde) değiştirme


97

Bir dizedeki birçok farklı alt dizeyi en verimli şekilde değiştirmem gerekiyor. string.replace kullanarak her alanı değiştirmenin kaba kuvvetinden başka bir yolu var mı?

Yanıtlar:


102

Üzerinde çalıştığınız dizge çok uzunsa veya birçok dizede çalışıyorsanız, bir java.util.regex.Matcher kullanmak faydalı olabilir (bu, derlemek için önceden zaman gerektirir, bu nedenle verimli olmayacaktır. Girişiniz çok küçükse veya arama modeliniz sık sık değişiyorsa).

Aşağıda, bir haritadan alınan jetonların listesine dayalı tam bir örnek verilmiştir. (Apache Commons Lang'den StringUtils kullanır).

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());

Normal ifade derlendikten sonra, girdi dizesini taramak genellikle çok hızlıdır (ancak normal ifadeniz karmaşıksa veya geriye dönük izleme içeriyorsa, bunu onaylamak için yine de kıyaslama yapmanız gerekir!)


1
Evet, yinelemelerin sayısı için kıyaslanması gerekiyor.
techzen

5
Bunu yapmadan önce her "%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
simgedeki

StringBuilder'ı biraz daha fazla hız için kullanabileceğinizi unutmayın. StringBuilder senkronize edilmedi. whoops'u düzenle sadece java 9 ile çalışıyor gerçi
Tinus Tate

3
Gelecekteki okuyucu: regex için, "(" ve ")", aranacak grubu kapsayacaktır. "%", Metinde gerçek olarak sayılır. Terimleriniz "%" ile başlamaz VE bitmezse, bulunmazlar. Bu nedenle, her iki kısımdaki (metin + kod) ön ekleri ve son ekleri ayarlayın.
linuxunil

66

Algoritma

Eşleşen dizeleri (normal ifadeler olmadan) değiştirmenin en etkili yollarından biri, Aho-Corasick algoritmasını performant Trie ("dene" olarak telaffuz edilir), hızlı hash algoritması ve verimli koleksiyon uygulaması ile kullanmaktır.

Basit Kod

Basit bir çözüm, Apache'nin StringUtils.replaceEachaşağıdaki gibi kullanır:

  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 );
  }

Bu, büyük metinlerde yavaşlar.

Hızlı Kod

Bor'un Aho-Corasick algoritmasını uygulaması, aynı yöntem imzasına sahip bir cephe kullanarak uygulama ayrıntısı haline gelen biraz daha karmaşıklık getirir:

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

Kıyaslamalar

Kıyaslamalar için, arabellek randomNumeric kullanılarak aşağıdaki gibi oluşturuldu :

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

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

MATCHES_DIVISOREnjekte edilecek değişkenlerin sayısını nerede belirler:

  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 ) );
    }
  }

Kıyaslama kodunun kendisi ( JMH aşırı görünüyordu):

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

1.000.000: 1.000

1.000.000 karakter ve değiştirilecek 1.000 rastgele yerleştirilmiş dizeden oluşan basit bir mikro ölçüt.

  • testStringUtils: 25 saniye, 25533 milis
  • testBorAhoCorasick: 0 saniye, 68 milis

Yarışma yok.

10.000: 1.000

10.000 karakter ve 1.000 eşleşen dize kullanma

  • testStringUtils: 1 saniye, 1402 milis
  • testBorAhoCorasick: 0 saniye, 37 milis

Bölünme kapanır.

1.000: 10

Değiştirmek için 1.000 karakter ve 10 eşleşen dize kullanma:

  • testStringUtils: 0 saniye, 7 milis
  • testBorAhoCorasick: 0 saniye, 19 milis

Kısa ipler için, Aho-Corasick'i kurmanın ek yükü, kaba kuvvet yaklaşımını gölgede bırakıyor StringUtils.replaceEach.

Her iki uygulamadan da en iyi şekilde yararlanmak için metin uzunluğuna dayalı karma bir yaklaşım mümkündür.

Uygulamalar

Aşağıdakiler dahil 1 MB'den uzun metinler için diğer uygulamaları karşılaştırmayı düşünün:

Bildiriler

Algoritma ile ilgili makaleler ve bilgiler:


5
Bu soruyu yeni ve değerli bilgilerle güncellediğiniz için tebrikler, bu çok güzel. En azından 10,000: 1,000 ve 1,000: 10 gibi makul değerler için bir JMH kıyaslamasının hala uygun olduğunu düşünüyorum (JIT bazen sihirli optimizasyonlar yapabilir).
Tunaki

builder.onlyWholeWords () 'ü kaldırın ve dize değiştirmeye benzer şekilde çalışacaktır.
Ondrej Sotolar

Bu mükemmel cevap için çok teşekkür ederim. Bu kesinlikle çok yardımcı oluyor! Sadece iki yaklaşımı karşılaştırmak ve daha anlamlı bir örnek vermek için, Trie'yi ikinci yaklaşımda sadece bir kez inşa etmek ve birçok farklı girdi dizisine uygulamak gerektiğini yorumlamak istedim. Trie'ye karşı StringUtils'e erişmenin ana avantajı bence: sadece bir kez oluşturursunuz. Yine de bu cevap için çok teşekkür ederim. İkinci yaklaşımı uygulamak için kullanılan metodolojiyi çok iyi paylaşıyor
Vic Seedoubleyew

Harika bir nokta, @VicSeedoubleyew. Cevabı güncellemek ister misin?
Dave Jarvis

10

Bu benim için çalıştı:

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

Misal:

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

Çıktı: apple-banana-frui-


Tam olarak arkadaşıma ihtiyacım olan şey :)
GOXR3PLUS

7

Bir String'i birçok kez değiştirecekseniz, genellikle bir StringBuilder kullanmak daha etkilidir (ancak öğrenmek için performansınızı ölçün) :

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();

Bir String üzerinde her değişiklik yaptığınızda, yeni bir String nesnesi oluşturulur, çünkü Strings değişmezdir. StringBuilder değiştirilebilir, yani istediğiniz kadar değiştirilebilir.


Korkarım yardımcı olmuyor. Değiştirme uzunluğu orijinalden farklı olduğunda, biraz kaymaya ihtiyacınız olacak, bu da dizgiyi yeniden oluşturmaktan daha maliyetli olabilir. Yoksa bir şey mi kaçırıyorum?
maaartinus

4

StringBuilderkarakter dizisi tamponu gerekli uzunlukta belirtilebildiğinden, değiştirme işlemini daha verimli bir şekilde gerçekleştirecektir. StringBuildereklemekten daha fazlası için tasarlanmıştır!

Tabii ki asıl soru, bunun çok uzak bir optimizasyon olup olmadığı? JVM, birden çok nesnenin oluşturulması ve sonraki çöp toplama işlemlerinde çok iyidir ve tüm optimizasyon soruları gibi, ilk sorum bunu ölçüp ölçmediğiniz ve bunun bir sorun olduğunu belirleyip belirlemediğinizdir.


2

ReplaceAll () yöntemini kullanmaya ne dersiniz ?


4
Pek çok farklı alt dizge bir normal ifadede işlenebilir (/substring1|substring2|.../). Her şey, OP'nin ne tür bir değişiklik yapmaya çalıştığına bağlı.
Avi

4
OP, daha verimli bir şey arıyorstr.replaceAll(search1, replace1).replaceAll(search2, replace2).replaceAll(search3, replace3).replaceAll(search4, replace4)
Kip

2

Rythm bir java şablon motoru şimdi String enterpolasyon modu adlı yeni bir özellik ile piyasaya sürüldü ve bu da aşağıdakiler gibi bir şey yapmanıza izin veriyor:

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

Yukarıdaki durum, argümanı şablona konuma göre geçirebileceğinizi gösterir. Rythm ayrıca argümanları ada göre iletmenize izin verir:

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

Not Rythm, ÇOK HIZLI, String.format ve hızdan yaklaşık 2 ila 3 kat daha hızlıdır, çünkü şablonu java bayt kodunda derler, çalışma zamanı performansı StringBuilder ile birleştirmeye çok yakındır.

Bağlantılar:


Bu, velocity, JSP even gibi çok sayıda şablon diliyle kullanılabilen çok çok eski bir özelliktir. Ayrıca, arama dizelerinin önceden tanımlanmış herhangi bir biçimde olmasını gerektirmeyen soruyu da yanıtlamaz.
Angsuman Chakraborty

İlginçtir, kabul edilen cevap bir örnek sunar: "%cat% really needs some %beverage%."; Bu %ayrılmış simge önceden tanımlanmış bir format değil mi? İlk noktanız daha da komik, JDK birçok "eski yetenek" sunuyor, bazıları 90'lı yıllardan başlıyor, insanlar neden bunları kullanmakla uğraşıyor? Yorumlarınız ve olumsuz oylarınız hiç mantıklı gelmiyor
Gelin Luo

Önceden var olan birçok şablon motoru varken ve Velocity veya Freemarker gibi önyükleme için yaygın olarak kullanılırken Rythm şablon motorunu tanıtmanın amacı nedir? Ayrıca, temel Java işlevleri yeterliyken neden başka bir ürün tanıtın. Performansla ilgili ifadenden gerçekten şüpheliyim çünkü Pattern de derlenebilir. Bazı gerçek sayıları görmek isterim.
Angsuman Chakraborty

Yeşil, noktayı kaçırıyorsun. Soruyu soran kişi rastgele dizeleri değiştirmek isterken, çözümünüz yalnızca @ öncül gibi önceden tanımlanmış biçimdeki dizeleri değiştirecektir. Evet, örnek% kullanır, ancak sınırlayıcı bir faktör olarak değil, yalnızca örnek olarak. Yani cevap vermeniz soruya ve dolayısıyla olumsuz noktaya cevap vermiyor.
Angsuman Chakraborty

2

Aşağıdakiler Todd Owen'in cevabına dayanmaktadır . Bu çözümde, değiştirmelerin normal ifadelerde özel anlamı olan karakterler içermesi durumunda beklenmedik sonuçlar elde etme sorunu vardır. Ayrıca isteğe bağlı olarak büyük / küçük harfe duyarlı olmayan bir arama yapabilmek istedim. İşte bulduğum şey:

/**
 * 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();
}

İşte benim birim test durumlarım:

@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;
}

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

1

Şunu bir kontrol et:

String.format(str,STR[])

Örneğin:

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

0

Özet: İki algoritmadan en verimli olanını otomatik olarak seçmek için Dave'in cevabının tek sınıf uygulaması.

Bu, Dave Jarvis'in yukarıdaki mükemmel cevabına dayanan tam, tek sınıflı bir uygulamadır . Sınıf, maksimum verimlilik için sağlanan iki farklı algoritma arasında otomatik olarak seçim yapar. (Bu cevap, hızlıca kopyalayıp yapıştırmak isteyenler içindir.)

ReplaceStrings sınıfı:

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("----------------------------------------------");
    }
}

Gerekli Maven bağımlılıkları:

(Gerekirse bunları pom dosyanıza ekleyin.)

    <!-- 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>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.