Comment vérifier si une chaîne contient une autre chaîne de manière insensible à la casse en Java?

387

Dis que j'ai deux cordes,

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

Je veux effectuer un retour de vérification qui s2est contenu dans s1. Je peux le faire avec:

return s1.contains(s2);

Je suis à peu près sûr que cela contains()respecte la casse, mais je ne peux pas le déterminer avec certitude en lisant la documentation. Si c'est le cas, je suppose que ma meilleure méthode serait quelque chose comme:

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

Tout cela mis à part, existe-t-il une autre façon (peut-être meilleure) d'accomplir cela sans se soucier de la sensibilité à la casse?

Aaron
la source
DrJava serait un moyen extrêmement simple de tester cela lorsque la documentation vous fait défaut. Tapez simplement quelques cas de test dans sa fenêtre Interactions, et vous devriez le découvrir.
EfForEffort
17
Je pense que vous avez répondu à votre propre question. Je ne pense pas que l'une des solutions ci-dessous soit meilleure que cela. Mais ils sont nettement plus lents.
Nikolay Dimitrov
7
Votre solution est plus simple que toutes celles dans les réponses
LobsterMan
2
La réponse que moi et beaucoup ici recherchons est dans votre question.
Lalit Fauzdar
1
Votre exemple est le plus simple, le plus lisible et probablement le meilleur moyen de le faire - mieux que toutes les réponses que je vois.
user1258361

Réponses:

320

Oui, contient est sensible à la casse. Vous pouvez utiliser java.util.regex.Pattern avec l'indicateur CASE_INSENSITIVE pour la correspondance insensible à la casse:

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

EDIT: Si s2 contient des caractères spéciaux regex (dont il y en a beaucoup), il est important de le citer d'abord. J'ai corrigé ma réponse car c'est la première que les gens verront, mais votez pour Matt Quail depuis qu'il l'a souligné.

Dave L.
la source
23
Comme indiqué dans la documentation de Pattern.CASE_INSENSITIVE, cela ne fonctionne que pour les caractères ASCII (c'est-à-dire que "Ä" ne correspondra pas à "ä"). Il faut également spécifier le UNICODE_CASEdrapeau pour y parvenir.
Philipp Wendler
72
cette approche utilise-t-elle Patternplus performante que s1.toLowerCase().contains(s2.toLowerCase())?
Rajat Gupta
6
@ user01 J'ai effectué une analyse de vitesse. Voir ma réponse pour les résultats (j'ai également montré une solution plus rapide): stackoverflow.com/a/25379180/1705598
icza
10
Je serais plus clair sur ce qui se passait si nous avions de meilleurs noms de variables:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
John Bowers
5
L'exactitude @ user01 précède les performances et l'utilisation de toLowerCase donnera des résultats potentiellement incorrects (par exemple, lors de la comparaison de certains textes grecs contenant la lettre Sigma, qui a deux formes minuscules pour la même forme majuscule).
Klitos Kyriacou
267

Un problème avec la réponse de Dave L. est lorsque s2 contient un balisage d'expression régulière tel que \d, etc.

Vous voulez appeler Pattern.quote () sur s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Matt Quail
la source
1
Belle prise Matt. Je suis curieux de savoir quelle méthode est la plus efficace - le minuscule contient ou votre solution de modèle. L'utilisation d'un modèle n'est-elle pas moins efficace pour une seule comparaison, mais plus efficace pour plusieurs comparaisons?
Aaron
41
La méthode .toLowerCase (). Contains () sera probablement plus rapide dans la plupart des cas. Je préférerais probablement ce style pour une complexité moindre aussi.
Matt Quail
3
@AaronFerguson Oui, en effet, toLowerCase().contains()c'est plus rapide. J'ai effectué une analyse de vitesse, voir ma réponse pour les résultats: stackoverflow.com/a/25379180/1705598
icza
2
@MattQuail ne sert à rien d'être plus rapide s'il est incorrect. Par exemple, le sigma grec a deux formes en minuscules (selon qu'il se trouve à la fin d'un mot ou non) et lorsque vous essayez de faire une correspondance de sous-chaîne insensible à la casse, où la sous-chaîne se termine par un sigma, vous pouvez facilement obtenir une erreur résultats.
Klitos Kyriacou
Je pense que nous devrions également ajouter un Pattern.UNICODE_CASEdrapeau. Pourriez-vous s'il vous plaît confirmer cela?
Thariq Nugrohotomo
160

Vous pouvez utiliser

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

La bibliothèque Apache Commons est très utile pour ce genre de chose. Et celle-ci peut être meilleure que les expressions régulières car l'expression régulière est toujours coûteuse en termes de performances.

muhamadto
la source
1
Est-ce que quelqu'un sait si cela respecte les paramètres régionaux?
Charles Wood
12
@CharlesWood Il délègue à String.regionMatches, qui utilise des conversions par caractère, donc non. De plus, containsIgnoreCase("ß", "ss")retourne -1, ce qui est faux dans tous les paramètres régionaux (le "sharp s" allemand capitalise en "ss".
maaartinus
Quelle serait alors la bonne façon de comparer les mots allemands? Il semble que ce soit une langue qui complique toutes les façons de comparer les chaînes: P
chomp
1
BTW: la langue allemande a été officiellement étendue avec un ß majuscule en 2017: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . Sur les claviers allemands, tapez Shift + Alt Gr + ß -> test: ẞ 😁
Kawu
119

Une mise en œuvre plus rapide: utilisation String.regionMatches()

L'utilisation d'expressions régulières peut être relativement lente. Cela (être lent) n'a pas d'importance si vous voulez simplement vérifier dans un cas. Mais si vous avez un tableau ou une collection de milliers ou de centaines de milliers de chaînes, les choses peuvent devenir assez lentes.

La solution présentée ci-dessous n'utilise pas d'expressions régulières ni toLowerCase()(ce qui est également lent car il crée une autre chaîne et la jette juste après la vérification).

La solution s'appuie sur la méthode String.regionMatches () qui semble inconnue. Il vérifie si 2 Stringrégions correspondent, mais ce qui est important, c'est qu'il a également une surcharge avec un ignoreCaseparamètre pratique .

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

Analyse de vitesse

Cette analyse de la vitesse ne signifie pas être une science de fusée, juste une image approximative de la rapidité des différentes méthodes.

Je compare 5 méthodes.

  1. Notre méthode containsIgnoreCase () .
  2. En convertissant les deux chaînes en minuscules et en appelant String.contains().
  3. En convertissant la chaîne source en minuscules et appelez String.contains()avec la sous-chaîne pré-mise en cache et en minuscules. Cette solution n'est déjà pas aussi flexible car elle teste une sous-chaîne prédéfinie.
  4. Utiliser l'expression régulière (la réponse acceptée Pattern.compile().matcher().find()...)
  5. Utilisation d'expressions régulières mais avec pré-création et mise en cache Pattern. Cette solution n'est déjà pas aussi flexible car elle teste une sous-chaîne prédéfinie.

Résultats (en appelant la méthode 10 millions de fois):

  1. Notre méthode: 670 ms
  2. 2x toLowerCase () et contient (): 2829 ms
  3. 1x toLowerCase () et contient () avec sous-chaîne en cache: 2446 ms
  4. Expression rationnelle: 7180 ms
  5. Expression régulière avec mise en cache Pattern: 1845 ms

Résultats dans un tableau:

                                            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

Notre méthode est 4 fois plus rapide par rapport à l'utilisation de minuscules et à l'utilisation contains(), 10 fois plus rapide que l' utilisation d'expressions régulières et également 3 fois plus rapide même si le Patternest pré-mis en cache (et perd la flexibilité de vérifier une sous-chaîne arbitraire).


Code de test d'analyse

Si vous êtes intéressé par la façon dont l'analyse a été effectuée, voici l'application exécutable complète:

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

}
icza
la source
6
+1 mais notez qu'il échoue pour ß(S allemand pointu; en majuscule pour SS) et aussi pour certains autres personnages (voir la source deString.regionMatches , qui essaie les deux conversions).
maaartinus
2
Vous testez toujours les mêmes cordes, ce qui n'est pas vraiment une comparaison équitable. «je suis» est toujours au milieu, ce qui peut ou non faire une différence pour les différentes méthodes de recherche. Mieux serait de générer des chaînes aléatoires et également de rendre compte de la vitesse lorsqu'une sous-chaîne n'est pas présente.
2
Cela semble vraiment proche de la méthode Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/…
alain.janinm
1
@ alain.janinm Je ne vois pas les similitudes. La seule chose qui semble "proche" StringUtils.containsIgnoreCase()est que ma solution et celle d'Apache utilisent une regionMatches()méthode (dans un cycle), mais même ce n'est pas la même chose que j'appelle String.regionMatches()et les appels Apache CharSequenceUtils.regionMatches().
icza
2
@icza CharSequenceUtils.regionMatchesappelle juste en String.regionMatchesfait. Quoi qu'il en soit, mon point était de donner l'info, que si quelqu'un utilise déjà la bibliothèque StringUtils, il peut simplement l'appeler parce que cela semble être un moyen efficace comme vous le prouvez avec votre référence. Si je n'utilisais pas Apache lib, j'utiliserais définitivement votre méthode;)
alain.janinm
22

Une façon plus simple de le faire (sans se soucier de la correspondance des modèles) serait de convertir les deux Strings en minuscules:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}
Phil
la source
4
La casse des caractères dépend de la langue, ce qui signifie qu'elle fonctionnera sur votre ordinateur mais échouera pour le client :). voir le commentaire @Adriaan Koster.
kroiz
1
@kroiz, cela dépend d'où vient la chaîne. La comparaison de "foobar" et "FOO" correspondra toujours, cependant si vous comparez des informations saisies par l'utilisateur ou du contenu spécifique à une langue, alors vous avez raison - un développeur doit être prudent.
Phil
16

Oui, c'est réalisable:

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;

Ce code renverra la chaîne "TRUE!" car il a constaté que vos personnages étaient contenus.

Bilbo Baggins
la source
12
Un gros inconvénient de l'utilisation de toLowerCase () est que le résultat dépend des paramètres régionaux actuels. Voir: javapapers.com/core-java/…
Adriaan Koster
4
La question contient en fait une meilleure solution car celle-ci échoue pour les non minuscules s2. Ne pas parler de tels détails comme celui-ci ne se compile pas et s'il le faisait, il retournerait une chaîne.
maaartinus
3

Voici quelques-uns compatibles avec Unicode que vous pouvez créer si vous utilisez ICU4j. Je suppose que "ignorer la casse" est discutable pour les noms de méthode, car bien que les comparaisons de force principales ignorent la casse, elles sont décrites comme étant spécifiques aux paramètres régionaux. Mais il est à espérer que cela dépend des paramètres régionaux d'une manière attendue par l'utilisateur.

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();
}
Trejkaz
la source
3

J'ai fait un test pour trouver une correspondance insensible à la casse d'une chaîne. J'ai un vecteur de 150 000 objets tous avec une chaîne comme un champ et je voulais trouver le sous-ensemble qui correspondait à une chaîne. J'ai essayé trois méthodes:

  1. Convertir tout en minuscules

    for (SongInformation song: songs) {
        if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
                ...
        }
    }
  2. Utilisez la méthode String matches ()

    for (SongInformation song: songs) {
        if (song.artist.matches("(?i).*" + pattern + ".*")) {
        ...
        }
    }
  3. Utilisez des expressions régulières

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

Les résultats de chronométrage sont:

  • Aucune tentative de correspondance: 20 ms

  • Pour réduire la correspondance: 182 ms

  • Correspondances de chaînes: 278 ms

  • Expression régulière: 65 ms

L'expression régulière semble être la plus rapide pour ce cas d'utilisation.

Jan Newmarch
la source
C'est bien que vous mettiez des résultats de chronométrage. Tout le monde dit à quel point l'expression régulière est lente, mais en réalité, elle est très rapide si vous ne devez compiler l'expression régulière qu'une seule fois.
woot
1

Il existe un moyen simple et concis, en utilisant le drapeau regex (insensible à la casse {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)
 * */
Mr.Q
la source
0

Je ne sais pas quelle est votre question principale ici, mais oui, .contains est sensible à la casse.

SCdF
la source
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;
}

Fondamentalement, c'est une méthode qui prend deux chaînes. Il est censé être une version non sensible à la casse de contains (). Lorsque vous utilisez la méthode contains, vous voulez voir si une chaîne est contenue dans l'autre.

Cette méthode prend la chaîne qui est «sous» et vérifie si elle est égale aux sous-chaînes de la chaîne de conteneur qui sont égales en longueur au «sous». Si vous regardez la forboucle, vous verrez qu'elle itère dans des sous-chaînes (qui sont la longueur du "sous") sur la chaîne de conteneur.

Chaque itération vérifie si la sous-chaîne de la chaîne de conteneur correspond equalsIgnoreCaseau sous.

seth
la source
en gros, c'est une méthode qui prend deux chaînes. il s'agit d'une version non sensible à la casse de contains (). lorsque vous utilisez la méthode contains, vous voulez voir si une chaîne est contenue dans l'autre. cette méthode prend la chaîne qui est "sub" et vérifie si elle est égale aux sous-chaînes de la chaîne conteneur, qui sont égales en longueur au "sub". si vous regardez la boucle for, vous verrez qu'elle itère dans les sous-chaînes (qui sont la longueur du "sous") sur la chaîne du conteneur. chaque itération vérifie si la sous-chaîne de la chaîne de conteneur est égale à la casse du sous-chaîne.
seth
@Vous devriez probablement ajouter cela à votre réponse.
The Guy with The Hat
2
C'est la méthode la plus lente de tous les temps ... et échoue également pour l'allemand.
maaartinus
0

Si vous devez rechercher une chaîne ASCII dans une autre chaîne ASCII, telle qu'une URL , vous trouverez ma solution meilleure. J'ai testé la méthode et la mienne d'icza pour la vitesse et voici les résultats:

  • Le cas 1 a pris 2788 ms - regionMatches
  • Le cas 2 a pris 1520 ms - mon

Le code:

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));
}
Revertron
la source
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");
        }

    }

}
sgrillon
la source
Merci pour cet extrait de code, qui pourrait fournir une aide limitée à court terme. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez faites.
Toby Speight
0
"AbCd".toLowerCase().contains("abcD".toLowerCase())
Takhir Atamuratov
la source
2
Pouvez-vous améliorer votre réponse en expliquant comment votre code résout le problème?
Isuka
1
Cette réponse a déjà été suggérée dans de nombreuses autres réponses plus détaillées à cette question que d'autres ont fournies. Je ne pense pas que cette réponse serve à quelque chose ici.
DaveyDaveDave
0

Nous pouvons utiliser stream avec anyMatch et contient de 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;
    }

}
Soudipta Dutta
la source
0

ou vous pouvez utiliser une approche simple et convertir simplement le cas de la chaîne en cas de sous-chaîne, puis utiliser la méthode contient.

Syed Salman Hassan
la source
-1
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());
LIERRE
la source
-1

Vous pouvez simplement faire quelque chose comme ceci:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
Erick Kondela
la source