ti-enxame.com

Como verificar se uma String contém outra String sem distinção entre maiúsculas e minúsculas em Java?

Digamos que eu tenha duas cordas

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

Quero realizar uma verificação retornando que s2 está contido em s1. Eu posso fazer isso com:

return s1.contains(s2);

Tenho certeza de que contains() é sensível a maiúsculas e minúsculas, no entanto, não posso determinar isso com certeza lendo a documentação. Se for então, suponho que meu melhor método seria algo como:

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

Tudo isso de lado, existe outra maneira (possivelmente melhor) de conseguir isso sem se preocupar com a diferenciação de maiúsculas e minúsculas?

351
Aaron

Sim, contém é sensível a maiúsculas e minúsculas. Você pode usar o Java.util.regex.Pattern com o sinalizador CASE_INSENSITIVE para a correspondência insensível a maiúsculas e minúsculas:

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

EDIT: Se s2 contém caracteres especiais de regex (dos quais existem muitos) é importante citá-lo primeiro. Eu corrigi a minha resposta, já que é a primeira que as pessoas vão ver, mas vote no Matt Quail desde que ele apontou isso.

300
Dave L.

Um problema com a resposta de Dave L. é quando s2 contém marcação regex como \d, etc.

Você quer chamar Pattern.quote () em s2:

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

Você pode usar

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

A biblioteca Apache Commons é muito útil para esse tipo de coisa. E esse em particular pode ser melhor do que expressões regulares, pois o regex é sempre caro em termos de desempenho.

136
muhamadto

Uma implementação mais rápida: utilizando String.regionMatches()

O uso de regexp pode ser relativamente lento. Isso (sendo lento) não importa se você quer apenas verificar em um caso. Mas se você tiver um array ou uma coleção de milhares ou centenas de milhares de strings, as coisas podem ficar muito lentas.

A solução apresentada abaixo não usa expressões regulares nem toLowerCase() (que também é lento porque cria outras strings e simplesmente as joga fora depois da checagem).

A solução baseia-se no método String.regionMatches () que parece ser desconhecido. Ele verifica se duas regiões String correspondem, mas o que é importante é que também tem uma sobrecarga com um parâmetro ignoreCase acessível.

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

Análise rápida

Esta análise de velocidade não significa ser ciência de foguetes, apenas uma imagem aproximada da rapidez com que os diferentes métodos são.

Eu comparo 5 métodos.

  1. Nosso método containsIgnoreCase () .
  2. Convertendo ambas as strings para minúsculas e chamando String.contains().
  3. Convertendo a string de origem para minúscula e chamando String.contains() com a subcadeia pré-armazenada em cache e com a caixa inferior. Essa solução já não é tão flexível porque testa uma substring predefinida.
  4. Usando expressão regular (a resposta aceita Pattern.compile().matcher().find()...)
  5. Usando expressão regular, mas com Pattern pré-criado e armazenado em cache. Essa solução já não é tão flexível porque testa uma substring predefinida.

Resultados (chamando o método 10 milhões de vezes):

  1. Nosso método: 670 ms
  2. 2x toLowerCase () e contém (): 2829 ms
  3. 1x toLowerCase () e contains () com substring em cache: 2446 ms
  4. Regexp: 7180 ms
  5. Regexp com cache Pattern: 1845 ms

Resultados em uma tabela:

                                            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

Nosso método é 4x mais rápido comparado com letras minúsculas e usando contains(), 10x mais rápido comparado ao uso de expressões regulares e também 3x mais rápido mesmo se o Pattern for pré-armazenado em cache (e perder a flexibilidade de verificar uma substring arbitrária).


Código de teste de análise

Se você estiver interessado em como a análise foi realizada, aqui está o aplicativo completo executável:

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

}
107
icza

Uma maneira mais simples de fazer isso (sem se preocupar com a correspondência de padrões) seria converter Strings em minúsculas:

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

Sim, isso é possível:

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;

Este código retornará a string "TRUE!" como descobriu que seus personagens estavam contidos.

16
Bilbo Baggins

Você pode usar expressões regulares , e funciona:

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

Eu fiz um teste para encontrar uma correspondência de uma string sem distinção entre maiúsculas e minúsculas. Eu tenho um vetor de 150.000 objetos, todos com um String como um campo e queria encontrar o subconjunto que corresponde a uma string. Eu tentei três métodos:

  1. Converta tudo para minúsculas

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

    for (SongInformation song: songs) {
        if (song.artist.matches("(?i).*" + pattern + ".*")) {
        ...
        }
    }
    
  3. Use expressões regulares

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

Os resultados do tempo são:

  • Nenhuma tentativa de correspondência: 20 ms

  • Para diminuir o jogo: 182 ms

  • Jogos de cordas: 278 ms

  • Expressão regular: 65 ms

A expressão regular parece ser a mais rápida para este caso de uso.

3
Jan Newmarch

Aqui estão alguns que podem ser feitos com Unicode se você puxar o ICU4j. Eu acho que "ignorar maiúsculas e minúsculas" é questionável para os nomes de método porque, embora comparações de força primária ignorem maiúsculas e minúsculas, ele é descrito como as especificidades sendo dependente de localidade. Mas espera-se que o locale seja dependente da maneira que o usuário espera.

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

Não sei qual é a sua principal questão aqui, mas sim, .contains é sensível a maiúsculas e minúsculas.

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

Podemos usar o stream com anyMatch e contém o 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;
}

Basicamente, é um método que leva duas seqüências. Supõe-se que seja uma versão não sensível a maiúsculas e minúsculas de contains (). Ao usar o método contains, você deseja ver se uma string está contida na outra.

Este método usa a string que é "sub" e verifica se é igual ao substrings da string container que são iguais em comprimento ao "sub". Se você olhar o loop for, verá que itera em substrings (que são o comprimento do "sub") sobre a string do container.

Cada iteração verifica se a subseqüência da string do contêiner é equalsIgnoreCase para o sub.

0
seth

Se você tiver que procurar uma string ASCIIem outra string ASCII, como um URL , você achará minha solução melhor. Eu testei o método do icza e o meu para a velocidade e aqui estão os resultados:

  • Caso 1 levou 2788 ms - regionMatches
  • Caso 2 levou 1520 ms - meu

O código:

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

Existe uma maneira simples e concisa, usando flag regex (case insensitive {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
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