Come verificare se una stringa contiene un'altra stringa in modo non sensibile al maiuscolo / minuscolo in Java?


386

Dì che ho due stringhe,

String s1 = "AbBaCca";
String s2 = "bac";

Voglio eseguire un controllo di ritorno che s2è contenuto all'interno s1. Posso farlo con:

return s1.contains(s2);

Sono abbastanza sicuro che contains()distingue tra maiuscole e minuscole, tuttavia non posso determinarlo con certezza leggendo la documentazione. Se è allora suppongo che il mio metodo migliore sarebbe qualcosa di simile:

return s1.toLowerCase().contains(s2.toLowerCase());

A parte questo, c'è un altro (forse migliore) modo per farlo senza preoccuparsi della distinzione tra maiuscole e minuscole?


DrJava sarebbe un modo estremamente semplice per testarlo quando la documentazione non riesce. Digita solo un paio di casi di test nella finestra Interazioni e dovresti scoprirlo.
EfForEffort,

17
Penso che tu abbia risposto alla tua stessa domanda. Non credo che nessuna delle soluzioni seguenti sia migliore di questa. Ma sono decisamente più lenti.
Nikolay Dimitrov,

7
La tua soluzione è più semplice di una qualsiasi delle risposte
LobsterMan

2
La risposta che io e molti qui stiamo cercando è nella tua domanda.
Lalit Fauzdar,

1
Il tuo esempio è il modo più semplice, più leggibile e probabilmente il modo migliore per farlo - meglio di una qualsiasi delle risposte che vedo.
user1258361

Risposte:


320

Sì, contiene la distinzione tra maiuscole e minuscole. Puoi usare java.util.regex.Pattern con il flag CASE_INSENSITIVE per la corrispondenza senza distinzione tra maiuscole e minuscole:

Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find();

EDIT: Se s2 contiene caratteri speciali regex (di cui ce ne sono molti) è importante citarlo prima. Ho corretto la mia risposta poiché è la prima che la gente vedrà, ma vota Matt Quail da quando lo ha sottolineato.


23
Come indicato dalla documentazione per Pattern.CASE_INSENSITIVE, questo funziona solo per i caratteri ASCII (ovvero, "Ä" non corrisponde a "ä"). È necessario specificare ulteriormente la UNICODE_CASEbandiera per raggiungere questo obiettivo.
Philipp Wendler,

72
questo approccio utilizza Patternpiù performanti di s1.toLowerCase().contains(s2.toLowerCase())?
Rajat Gupta,

6
@ user01 Ho eseguito un'analisi della velocità. Vedere la mia risposta per i risultati (ho anche mostrato una soluzione più veloce): stackoverflow.com/a/25379180/1705598
icza

10
Mi farebbe più chiaro cosa succederebbe se avessimo nomi variabili migliori:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
John Bowers,

5
La correttezza di @utente01 viene prima delle prestazioni e l'uso di toLowerCase darà risultati potenzialmente errati (ad esempio, quando si confronta un determinato testo greco contenente la lettera Sigma, che ha due forme minuscole per la stessa forma maiuscola).
Klitos Kyriacou,

267

Un problema con la risposta di Dave L. è quando s2 contiene markup regex come \d, ecc.

Vuoi chiamare Pattern.quote () su s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();

1
Bella cattura Matt. Sono curioso di sapere quale metodo è più efficiente: contiene le lettere minuscole o la tua soluzione di pattern. L'uso di un modello non è meno efficiente per un singolo confronto, ma è più efficiente per più confronti?
Aaron,

41
Il metodo .toLowerCase (). Includes () sarà probabilmente più veloce nella maggior parte dei casi. Preferirei probabilmente quello stile anche per una complessità inferiore.
Matt Quail,

3
@AaronFerguson Sì, infatti, toLowerCase().contains()è più veloce. Ho eseguito alcune analisi di velocità, vedo la mia risposta per i risultati: stackoverflow.com/a/25379180/1705598
icza

2
@MattQuail non ha senso essere più veloce se potrebbe essere errato. Ad esempio, il sigma della capitale greca ha due forme minuscole (a seconda che arrivi alla fine di una parola o meno) e quando si tenta di fare una corrispondenza di sottostringa senza distinzione tra maiuscole e minuscole, dove la sottostringa termina con un sigma, si potrebbe facilmente ottenere un errore risultati.
Klitos Kyriacou,

Penso che dovremmo aggiungere Pattern.UNICODE_CASEanche la bandiera. Potresti confermare questo?
Thariq Nugrohotomo,

160

Puoi usare

org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");

La biblioteca di Apache Commons è molto utile per questo genere di cose. E questo in particolare può essere migliore delle espressioni regolari poiché regex è sempre costoso in termini di prestazioni.


1
Qualcuno sa se questo rispetta le impostazioni locali?
Charles Wood,

12
@CharlesWood Delega a String.regionMatches, che utilizza conversioni dal punto di vista dei personaggi, quindi no. Inoltre, containsIgnoreCase("ß", "ss")restituisce -1, il che è errato in ogni locale (la "s" tedesca capitalizza in "ss".
maaartinus,

Quale sarebbe il modo giusto di confrontare le parole tedesche allora? Sembra che sia una lingua che complica ogni modo di confrontare le stringhe: P
chomp

1
A proposito: la lingua tedesca è stata ufficialmente estesa con un capitale ß nel 2017: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . Sulle tastiere tedesche, digita Maiusc + Alt Gr + ß -> test: ẞ 😁
Kawu

119

Un'implementazione più rapida: utilizzo String.regionMatches()

L'uso di regexp può essere relativamente lento. (Essendo lento) non importa se vuoi solo controllare in un caso. Ma se hai un array o una raccolta di migliaia o centinaia di migliaia di stringhe, le cose possono diventare piuttosto lente.

La soluzione presentata di seguito non utilizza espressioni regolari né toLowerCase()(che è anche lenta perché crea altre stringhe e le butta via dopo il controllo).

La soluzione si basa sul metodo String.regionMatches () che sembra essere sconosciuto. Controlla se 2 Stringregioni corrispondono, ma l'importante è che abbia anche un sovraccarico con un ignoreCaseparametro utile .

public static boolean containsIgnoreCase(String src, String what) {
    final int length = what.length();
    if (length == 0)
        return true; // Empty string is contained

    final char firstLo = Character.toLowerCase(what.charAt(0));
    final char firstUp = Character.toUpperCase(what.charAt(0));

    for (int i = src.length() - length; i >= 0; i--) {
        // Quick check before calling the more expensive regionMatches() method:
        final char ch = src.charAt(i);
        if (ch != firstLo && ch != firstUp)
            continue;

        if (src.regionMatches(true, i, what, 0, length))
            return true;
    }

    return false;
}

Analisi della velocità

Questa analisi della velocità non significa essere scienza missilistica, ma solo un quadro di quanto siano veloci i diversi metodi.

Confronto 5 metodi.

  1. Il nostro metodo contieneIgnoreCase () .
  2. Convertendo entrambe le stringhe in lettere minuscole e call String.contains().
  3. Convertendo la stringa di origine in lettere minuscole e chiamando String.contains()con la sottostringa precaricata e con lettere minuscole . Questa soluzione non è già così flessibile perché verifica una sottostringa predefinita.
  4. Utilizzo dell'espressione regolare (la risposta accettata Pattern.compile().matcher().find()...)
  5. Utilizzo dell'espressione regolare ma con pre-creato e memorizzato nella cache Pattern. Questa soluzione non è già così flessibile perché verifica una sottostringa predefinita.

Risultati (chiamando il metodo 10 milioni di volte):

  1. Il nostro metodo: 670 ms
  2. 2x toLowerCase () e contiene (): 2829 ms
  3. 1x toLowerCase () e contiene () con sottostringa memorizzata nella cache: 2446 ms
  4. Regexp: 7180 ms
  5. Riscrivi con cache Pattern: 1845 ms

Risultati in una tabella:

                                            RELATIVE SPEED   1/RELATIVE SPEED
 METHOD                          EXEC TIME    TO SLOWEST      TO FASTEST (#1)
------------------------------------------------------------------------------
 1. Using regionMatches()          670 ms       10.7x            1.0x
 2. 2x lowercase+contains         2829 ms        2.5x            4.2x
 3. 1x lowercase+contains cache   2446 ms        2.9x            3.7x
 4. Regexp                        7180 ms        1.0x           10.7x
 5. Regexp+cached pattern         1845 ms        3.9x            2.8x

Il nostro metodo è 4 volte più veloce rispetto al minuscolo e all'utilizzo contains(), 10 volte più veloce rispetto all'uso delle espressioni regolari e anche 3 volte più veloce anche se Patternè pre-memorizzato nella cache (e perde la flessibilità di controllo per una sottostringa arbitraria).


Codice del test di analisi

Se sei interessato a come è stata eseguita l'analisi, ecco l'applicazione eseguibile completa:

import java.util.regex.Pattern;

public class ContainsAnalysis {

    // Case 1 utilizing String.regionMatches()
    public static boolean containsIgnoreCase(String src, String what) {
        final int length = what.length();
        if (length == 0)
            return true; // Empty string is contained

        final char firstLo = Character.toLowerCase(what.charAt(0));
        final char firstUp = Character.toUpperCase(what.charAt(0));

        for (int i = src.length() - length; i >= 0; i--) {
            // Quick check before calling the more expensive regionMatches()
            // method:
            final char ch = src.charAt(i);
            if (ch != firstLo && ch != firstUp)
                continue;

            if (src.regionMatches(true, i, what, 0, length))
                return true;
        }

        return false;
    }

    // Case 2 with 2x toLowerCase() and contains()
    public static boolean containsConverting(String src, String what) {
        return src.toLowerCase().contains(what.toLowerCase());
    }

    // The cached substring for case 3
    private static final String S = "i am".toLowerCase();

    // Case 3 with pre-cached substring and 1x toLowerCase() and contains()
    public static boolean containsConverting(String src) {
        return src.toLowerCase().contains(S);
    }

    // Case 4 with regexp
    public static boolean containsIgnoreCaseRegexp(String src, String what) {
        return Pattern.compile(Pattern.quote(what), Pattern.CASE_INSENSITIVE)
                    .matcher(src).find();
    }

    // The cached pattern for case 5
    private static final Pattern P = Pattern.compile(
            Pattern.quote("i am"), Pattern.CASE_INSENSITIVE);

    // Case 5 with pre-cached Pattern
    public static boolean containsIgnoreCaseRegexp(String src) {
        return P.matcher(src).find();
    }

    // Main method: perfroms speed analysis on different contains methods
    // (case ignored)
    public static void main(String[] args) throws Exception {
        final String src = "Hi, I am Adam";
        final String what = "i am";

        long start, end;
        final int N = 10_000_000;

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCase(src, what);
        end = System.nanoTime();
        System.out.println("Case 1 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsConverting(src, what);
        end = System.nanoTime();
        System.out.println("Case 2 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsConverting(src);
        end = System.nanoTime();
        System.out.println("Case 3 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCaseRegexp(src, what);
        end = System.nanoTime();
        System.out.println("Case 4 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCaseRegexp(src);
        end = System.nanoTime();
        System.out.println("Case 5 took " + ((end - start) / 1000000) + "ms");
    }

}

6
+1 ma nota che fallisce per ß(S tedesco acuto; maiuscolo SS) e anche per alcuni altri personaggi (vedi la fonte di String.regionMatches, che prova entrambe le conversioni).
maaartinus,

2
Verifichi sempre le stesse stringhe, il che non è davvero un confronto equo. 'I am' è sempre nel mezzo, il che potrebbe o meno fare la differenza per i diversi metodi di ricerca. Meglio sarebbe generare stringhe casuali e riferire anche sulla velocità quando non è presente una sottostringa.

2
Sembra molto vicino al metodo Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/…
alain.janinm

1
@ alain.janinm Non riesco a vedere le somiglianze. L'unica cosa che sembra "vicina" StringUtils.containsIgnoreCase()è che sia la mia soluzione che quella Apache usano un regionMatches()metodo (in un ciclo), ma anche quello non è lo stesso che io chiamo String.regionMatches()e chiamate Apache CharSequenceUtils.regionMatches().
Icza,

2
@icza CharSequenceUtils.regionMatcheschiama String.regionMatchesdavvero. Comunque, il mio punto era quello di fornire le informazioni, che se qualcuno sta già usando la libreria StringUtils, può semplicemente chiamarlo perché sembra essere un modo efficiente come lo dimostrate con il vostro benchmark. Se non
usassi

22

Un modo più semplice per farlo (senza preoccuparsi della corrispondenza dei modelli) sarebbe convertire entrambe le Strings in minuscolo:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}

4
La custodia dei caratteri dipende dalla lingua, il che significa che funzionerà sul tuo computer ma non riuscirà per il cliente :). vedi il commento di @Adriaan Koster.
kroiz,

1
@kroiz, dipende da dove proviene la stringa. Il confronto tra "foobar" e "FOO" corrisponderà sempre, tuttavia se stai confrontando le informazioni di input dell'utente o il contenuto specifico della lingua, allora hai ragione: uno sviluppatore dovrebbe essere cauto.
Phil

16

Sì, questo è realizzabile:

String s1 = "abBaCca";
String s2 = "bac";

String s1Lower = s1;

//s1Lower is exact same string, now convert it to lowercase, I left the s1 intact for print purposes if needed

s1Lower = s1Lower.toLowerCase();

String trueStatement = "FALSE!";
if (s1Lower.contains(s2)) {

    //THIS statement will be TRUE
    trueStatement = "TRUE!"
}

return trueStatement;

Questo codice restituirà la stringa "TRUE!" poiché ha scoperto che i tuoi personaggi erano contenuti.


12
Un grosso svantaggio dell'uso di toLowerCase () è che il risultato dipende dalle impostazioni internazionali correnti. Vedi: javapapers.com/core-java/…
Adriaan Koster il

4
La domanda in realtà contiene una soluzione migliore in quanto questa non riesce per le lettere minuscole s2. Non parlando di dettagli come questo, questo non viene compilato e, se lo facesse, restituirebbe una stringa.
maaartinus,


3

Ecco alcuni di quelli compatibili con Unicode che puoi realizzare inserendo ICU4j. Immagino che "ignora caso" sia discutibile per i nomi dei metodi perché, sebbene i confronti di forza primaria ignorino il caso, è descritto come le specifiche dipendono dalla locale. Ma si spera che dipenda dalle impostazioni locali in un modo che l'utente si aspetterebbe.

public static boolean containsIgnoreCase(String haystack, String needle) {
    return indexOfIgnoreCase(haystack, needle) >= 0;
}

public static int indexOfIgnoreCase(String haystack, String needle) {
    StringSearch stringSearch = new StringSearch(needle, haystack);
    stringSearch.getCollator().setStrength(Collator.PRIMARY);
    return stringSearch.first();
}

3

Ho fatto un test per trovare una corrispondenza senza distinzione tra maiuscole e minuscole di una stringa. Ho un vettore di 150.000 oggetti tutti con una stringa come un campo e volevo trovare il sottoinsieme che corrispondesse a una stringa. Ho provato tre metodi:

  1. Converti tutto in minuscolo

    for (SongInformation song: songs) {
        if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
                ...
        }
    }
  2. Utilizzare il metodo String Match ()

    for (SongInformation song: songs) {
        if (song.artist.matches("(?i).*" + pattern + ".*")) {
        ...
        }
    }
  3. Usa espressioni regolari

    Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher("");
    for (SongInformation song: songs) {
        m.reset(song.artist);
        if (m.find()) {
        ...
        }
    }

I risultati dei tempi sono:

  • Nessuna corrispondenza tentata: 20 msec

  • Per ridurre la corrispondenza: 182 msec

  • Corrispondenze delle stringhe: 278 msec

  • Espressione regolare: 65 msec

L'espressione regolare sembra essere la più veloce per questo caso d'uso.


Bene che hai messo i risultati di temporizzazione. Tutti dicono quanto è lento regex, ma in realtà è molto veloce se devi compilare il regex solo una volta.
woot,

1

C'è un modo semplice e conciso, usando il flag regex (maiuscole e minuscole {i}):

 String s1 = "hello abc efg";
 String s2 = "ABC";
 s1.matches(".*(?i)"+s2+".*");

/*
 * .*  denotes every character except line break
 * (?i) denotes case insensitivity flag enabled for s2 (String)
 * */

0

Non sono sicuro di quale sia la tua domanda principale, ma sì. Contiene la distinzione tra maiuscole e minuscole.


0
String container = " Case SeNsitive ";
String sub = "sen";
if (rcontains(container, sub)) {
    System.out.println("no case");
}

public static Boolean rcontains(String container, String sub) {

    Boolean b = false;
    for (int a = 0; a < container.length() - sub.length() + 1; a++) {
        //System.out.println(sub + " to " + container.substring(a, a+sub.length()));
        if (sub.equalsIgnoreCase(container.substring(a, a + sub.length()))) {
            b = true;
        }
    }
    return b;
}

Fondamentalmente, è un metodo che richiede due stringhe. Dovrebbe essere una versione non sensibile al maiuscolo / minuscolo di contenga (). Quando si utilizza il metodo contiene, si desidera vedere se una stringa è contenuta nell'altra.

Questo metodo prende la stringa "sub" e controlla se è uguale alle sottostringhe della stringa del contenitore che sono uguali in lunghezza al "sub". Se guardi il forciclo, vedrai che scorre in sottostringhe (che sono la lunghezza del "sub") sopra la stringa del contenitore.

Ogni iterazione verifica se la sottostringa della stringa del contenitore è equalsIgnoreCaseal sottotitolo.


fondamentalmente è un metodo che richiede due stringhe. si suppone che sia una versione non sensibile al maiuscolo / minuscolo di Includes (). quando si utilizza il metodo contiene, si desidera vedere se una stringa è contenuta nell'altra. questo metodo prende la stringa "sub" e controlla se è uguale alle stringhe secondarie della stringa del contenitore, che sono uguali in lunghezza al "sub". se guardi il ciclo for vedrai che scorre tra le stringhe secondarie (che sono la lunghezza del "sottotitolo") sopra la stringa del contenitore. ogni iterazione verifica se la stringa secondaria della stringa contenitore è uguale a quella secondaria.
seth,

@ Probabilmente dovresti aggiungerlo alla tua risposta.
The Guy with The Hat,

2
Questo è il metodo più lento di sempre ... e fallisce anche per il tedesco.
maaartinus,

0

Se devi cercare una stringa ASCII in un'altra stringa ASCII, come un URL , troverai la mia soluzione migliore. Ho testato il metodo icza e il mio per la velocità e qui ci sono i risultati:

  • Il caso 1 ha richiesto 2788 ms - regionMatches
  • Il caso 2 ha richiesto 1520 ms - il mio

Il codice:

public static String lowerCaseAscii(String s) {
    if (s == null)
        return null;

    int len = s.length();
    char[] buf = new char[len];
    s.getChars(0, len, buf, 0);
    for (int i=0; i<len; i++) {
        if (buf[i] >= 'A' && buf[i] <= 'Z')
            buf[i] += 0x20;
    }

    return new String(buf);
}

public static boolean containsIgnoreCaseAscii(String str, String searchStr) {
    return StringUtils.contains(lowerCaseAscii(str), lowerCaseAscii(searchStr));
}

0
import java.text.Normalizer;

import org.apache.commons.lang3.StringUtils;

public class ContainsIgnoreCase {

    public static void main(String[] args) {

        String in = "   Annulée ";
        String key = "annulee";

        // 100% java
        if (Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").toLowerCase().contains(key)) {
            System.out.println("OK");
        } else {
            System.out.println("KO");
        }

        // use commons.lang lib
        if (StringUtils.containsIgnoreCase(Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""), key)) {
            System.out.println("OK");
        } else {
            System.out.println("KO");
        }

    }

}

Grazie per questo frammento di codice, che potrebbe fornire un aiuto limitato a breve termine. Una spiegazione adeguata migliorerebbe notevolmente il suo valore a lungo termine mostrando perché questa è una buona soluzione al problema e lo renderebbe più utile ai futuri lettori con altre domande simili. Si prega di modificare la risposta di aggiungere qualche spiegazione, tra le ipotesi che hai fatto.
Toby Speight,

0
"AbCd".toLowerCase().contains("abcD".toLowerCase())

2
Puoi migliorare la tua risposta spiegando come il tuo codice risolve il problema?
Isuka,

1
Questa risposta è già stata suggerita in molte altre, risposte più dettagliate a questa domanda che altri hanno fornito. Non credo che questa risposta abbia uno scopo qui.
DaveyDaveDave il

0

Possiamo usare lo stream con anyMatch e contiene Java 8

public class Test2 {
    public static void main(String[] args) {

        String a = "Gina Gini Protijayi Soudipta";
        String b = "Gini";

        System.out.println(WordPresentOrNot(a, b));
    }// main

    private static boolean WordPresentOrNot(String a, String b) {
    //contains is case sensitive. That's why change it to upper or lower case. Then check
        // Here we are using stream with anyMatch
        boolean match = Arrays.stream(a.toLowerCase().split(" ")).anyMatch(b.toLowerCase()::contains);
        return match;
    }

}

0

oppure puoi usare un approccio semplice e convertire il caso della stringa in caso della sottostringa e quindi usare il metodo contiene.


-1
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());

-1

Potresti semplicemente fare qualcosa del genere:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.