web-dev-qa-db-fra.com

Comment vérifier si une chaîne contient une autre chaîne sans tenir compte de la casse en Java?

Dis que j'ai deux ficelles,

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

Je veux effectuer une vérification en retournant que s2 est contenu dans s1. Je peux le faire avec:

return s1.contains(s2);

Je suis à peu près sûr que contains() est sensible à 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 un autre moyen (peut-être meilleur) d'accomplir cela sans se soucier de la sensibilité à la casse?

359
Aaron

Oui, contient est sensible à la casse. Vous pouvez utiliser Java.util.regex.Pattern avec l'indicateur CASE_INSENSITIVE pour la correspondance sans distinction de casse:

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

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

301
Dave L.

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

Vous voulez appeler Pattern.quote () sur s2:

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

Vous pouvez utiliser

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

La bibliothèque Apache Commons est très utile pour ce genre de choses. Et celle-ci peut être meilleure que les expressions régulières, car les expressions rationnelles sont toujours chères en termes de performances.

141
muhamadto

Une implémentation plus rapide: Utiliser String.regionMatches()

L'utilisation de l'expression rationnelle peut être relativement lente. Peu importe 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 très lentes.

La solution présentée ci-dessous n'utilise pas d'expressions régulières ni toLowerCase() (qui est également lente car elle crée d'autres chaînes et les jette simplement après la vérification).

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

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 sorcière, mais juste une idée 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 minuscule et en appelant String.contains() avec la sous-chaîne pré-mise en cache et en minuscule. Cette solution n’est déjà pas aussi flexible car elle teste une sous-chaîne de prédefiend.
  4. Utiliser une expression régulière (la réponse acceptée Pattern.compile().matcher().find()...)
  5. Utilisation d'une expression régulière mais avec Pattern pré-créé et mis en cache. 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 mise en cache: 2446 ms
  4. Expression rationnelle: 7180 ms
  5. Expression rationnelle avec mis 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 4x plus rapide par rapport à la minuscule et en utilisant contains(), 10x plus rapide par rapport à l'utilisation d'expressions régulières et également 3 fois plus vite même si le Pattern est précaché (et perd de la flexibilité pour la recherche d'une sous-chaîne arbitraire) .


Code de test d'analyse

Si vous souhaitez savoir comment l'analyse a été effectuée, voici l'application complète exécutable:

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

}
111
icza

Une façon plus simple de procéder (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!");
}
20
Phil

Oui, cela 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!" comme il a constaté que vos personnages ont été contenus.

16
Bilbo Baggins

Vous pouvez utiliser expressions régulières , et cela fonctionne:

boolean found = s1.matches("(?i).*" + s2+ ".*");
6
Shiv

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

  1. Convertir tout en minuscule

    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 diminuer la correspondance: 182 ms

  • Correspondance de chaîne: 278 ms

  • Expression régulière: 65 ms

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

3
Jan Newmarch

Voici des exemples compatibles avec Unicode que vous pouvez créer en intégrant ICU4j. J'imagine que "ignorer la casse" est discutable pour les noms de méthodes car, bien que les comparaisons de force principales ignorent la casse, elles sont décrites comme étant les spécificités dépendantes de la localisation. Mais, espérons-le, cela dépend des paramètres régionaux de la 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();
}
3
Trejkaz

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

1
SCdF
"AbCd".toLowerCase().contains("abcD".toLowerCase())
1
Takhir Atamuratov

Nous pouvons utiliser stream avec anyMatch et contient 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
Soudipta Dutta
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 supposé être une version non sensible à la casse de contient (). Lorsque vous utilisez la méthode contient, vous voulez voir si une chaîne est contenue dans l'autre.

Cette méthode prend la chaîne "sub" et vérifie si elle est égale aux sous-chaînes de la chaîne de conteneur de longueur égale à "sub". Si vous regardez la boucle for, vous verrez qu’elle se répète dans les sous-chaînes (qui sont la longueur du "sub") sur la chaîne de conteneur.

Chaque itération vérifie si la sous-chaîne de la chaîne de conteneur est equalsIgnoreCase pour le sous-fichier.

0
seth

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

  • Cas 1 a pris 2788 ms - régionMatches
  • 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));
}
0
Revertron

Il existe un moyen simple et concis d’utiliser 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)
 * */
0
Mr.Q

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

0
Syed Salman Hassan
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");
        }

    }

}
0
sgrillon