Como verificar se uma String contém outra String de uma maneira que não diferencia maiúsculas de minúsculas em Java?

386

Digamos que eu tenho duas cordas,

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

Quero executar uma verificação de retorno que s2esteja contida nele s1. Eu posso fazer isso com:

return s1.contains(s2);

Tenho certeza de que contains()diferencia maiúsculas de minúsculas, mas não posso determinar isso com certeza lendo a documentação. Se for, acho que meu melhor método seria algo como:

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

Tudo isso à parte, existe outra maneira (possivelmente melhor) de fazer isso sem se preocupar com a distinção entre maiúsculas e minúsculas?

Aaron
fonte
O DrJava seria uma maneira extremamente fácil de testar isso quando a documentação falhar. Basta digitar alguns casos de teste na janela Interações e você deve descobrir.
EfForEffort 17/09/08
17
Eu acho que você respondeu sua própria pergunta. Não acho que nenhuma das soluções abaixo seja melhor que isso. Mas eles são definitivamente mais lentos.
Nikolay Dimitrov
7
Sua solução é mais simples do que qualquer uma das respostas
LobsterMan
2
A resposta que eu e muitos aqui estamos procurando está na sua pergunta.
Lalit Fauzdar
11
Seu exemplo é o mais simples, mais legível e provavelmente a melhor maneira de fazer isso - melhor do que qualquer uma das respostas que estou vendo.
user1258361

Respostas:

320

Sim, contém diferencia maiúsculas de minúsculas. Você pode usar java.util.regex.Pattern com o sinalizador CASE_INSENSITIVE para correspondência sem distinção entre maiúsculas e minúsculas:

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

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

Dave L.
fonte
23
Conforme declarado na documentação de Pattern.CASE_INSENSITIVE, isso funciona apenas para caracteres ASCII (ou seja, "Ä" não corresponde a "ä"). É preciso especificar adicionalmente a UNICODE_CASEbandeira para conseguir isso.
Philipp Wendler
72
esta abordagem está usando Patternmais desempenho do que s1.toLowerCase().contains(s2.toLowerCase())?
Rajat Gupta
6
@ user01 Fiz uma análise de velocidade. Veja minha resposta para os resultados (eu também mostrei uma solução mais rápida): stackoverflow.com/a/25379180/1705598
icza
10
Ficaria mais claro o que estava acontecendo se tivéssemos nomes de variáveis ​​melhores:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
John Bowers
5
@ user01 a correção vem antes do desempenho e o uso de toLowerCase fornecerá resultados potencialmente incorretos (por exemplo, ao comparar determinado texto em grego contendo a letra Sigma, que possui duas formas em minúsculas para a mesma forma em maiúsculas).
Klitos Kyriacou
266

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

Você deseja chamar Pattern.quote () no s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Matt Quail
fonte
11
Boa captura Matt. Estou curioso para saber qual método é mais eficiente - a minúscula contém ou sua solução padrão. O uso de um padrão não é menos eficiente para uma única comparação, mas mais eficiente para várias comparações?
Aaron
41
O método .toLowerCase (). Contains () provavelmente será mais rápido na maioria dos casos. Eu provavelmente preferiria esse estilo por menor complexidade também.
Matt Quail
3
@AaronFerguson Sim, de fato, toLowerCase().contains()é mais rápido. Eu realizada uma análise rápida, consulte minha resposta para resultados: stackoverflow.com/a/25379180/1705598
icza
2
@ MattQuail Não há sentido em ser mais rápido se estiver incorreto. Por exemplo, o sigma maiúsculo grego tem duas formas em minúsculas (dependendo se vem no final de uma palavra ou não) e ao tentar fazer uma correspondência de substring sem distinção entre maiúsculas e minúsculas, onde o substring termina com um sigma, você pode facilmente ficar incorreto resultados.
Klitos Kyriacou
Eu acho que devemos adicionar Pattern.UNICODE_CASEbandeira também. Poderia confirmar isso?
Thariq Nugrohotomo
160

Você pode usar

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

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

muhamadto
fonte
11
Alguém sabe se isso respeita a localidade?
Charles Wood
12
@CharlesWood Delega para String.regionMatches, que usa conversões entre caracteres, portanto não. Além disso, containsIgnoreCase("ß", "ss")retorna-1, o que é errado em cada localidade (os alemães "s afiados" capitaliza com "ss".
maaartinus
Qual seria a maneira correta de comparar palavras em alemão? Parece que é uma linguagem que complica todas as formas de cordas comparando: P
chomp
11
BTW: o idioma alemão foi oficialmente ampliado com um capital ß em 2017: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . Nos teclados alemães, digite Shift + Alt Gr + ß -> test: ẞ 😁
Kawu 23/11/19
119

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

O uso do regexp pode ser relativamente lento. (Ser lento) não importa se você deseja apenas verificar em um caso. Mas se você tiver uma matriz ou uma coleção de milhares ou centenas de milhares de strings, as coisas podem ficar bem lentas.

A solução apresentada abaixo não usa expressões regulares nem toLowerCase()(o que também é lento porque cria outras strings e as joga fora após a verificação).

A solução baseia-se no método String.regionMatches () que parece ser desconhecido. Ele verifica se duas Stringregiões correspondem, mas o importante é que ele também tenha uma sobrecarga com um ignoreCaseparâmetro útil .

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

Essa análise de velocidade não significa 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 as duas strings para minúsculas e chamadas String.contains().
  3. Convertendo a cadeia de origem em minúscula e chamando String.contains()com a substring pré-armazenada em cache e em minúsculas. Essa solução já não é tão flexível porque testa uma substring de pré-amigo.
  4. Usando expressão regular (a resposta aceita Pattern.compile().matcher().find()...)
  5. Usando expressão regular, mas com pré-criado e armazenado em cache Pattern. 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 paraLowerCase () e contém (): 2829 ms
  3. 1x toLowerCase () e contém () 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 em comparação com letras minúsculas e usando contains(), 10x mais rápido em comparação com expressões regulares e também 3x mais rápido, mesmo se o Patterncache for pré-armazenado em cache (e perdendo a flexibilidade de verificar se há uma substring arbitrária).


Código do Teste de Análise

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

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
fonte
6
+1, mas observe que ele falha em ß(S acentuado em alemão; coloca em maiúscula em SS) e também em alguns outros caracteres (consulte a fonte de String.regionMatches, que tenta as duas conversões).
Maaartinus
2
Você sempre testa as mesmas strings, o que não é realmente uma comparação justa. 'eu sou' está sempre no meio, o que pode ou não fazer diferença para os diferentes métodos de pesquisa. Melhor seria gerar seqüências aleatórias e também informar a velocidade quando uma substring não estiver presente.
2
Isso parece realmente perto de método Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/...
alain.janinm
11
@ alain.janinm Não vejo as semelhanças. A única coisa que parece "próxima" StringUtils.containsIgnoreCase()é que tanto a minha solução quanto a Apache usam um regionMatches()método (em um ciclo), mas mesmo isso não é o mesmo que eu chamo String.regionMatches()e o Apache chama CharSequenceUtils.regionMatches().
icza
2
@icza CharSequenceUtils.regionMatchesapenas liga String.regionMatchesna verdade. Enfim, meu objetivo era fornecer as informações de que, se alguém já estiver usando o StringUtils lib, ele poderá chamá-lo, porque parece ser uma maneira eficiente de você provar isso com seu benchmark. Se eu não estivesse usando a biblioteca Apache, definitivamente usaria o seu método;)
alain.janinm 25/11/14
22

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

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}
Phil
fonte
4
As letras maiúsculas e minúsculas dependem do idioma, o que significa que funcionará no seu computador, mas falhará para o cliente :). veja o comentário de @Adriaan Koster.
Kriz
11
@ kroiz, isso depende de onde a String veio. Comparar "foobar" e "FOO" sempre corresponderá; no entanto, se você estiver comparando informações de entrada do usuário ou conteúdo específico do idioma, você está certo - um desenvolvedor deve ser cauteloso.
23412 Phil
16

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!" pois descobriu que seus personagens estavam contidos.

Bilbo Bolseiro
fonte
12
Uma grande desvantagem do uso de toLowerCase () é que o resultado depende da localidade atual. Veja: javapapers.com/core-java/…
Adriaan Koster
4
Na verdade, a pergunta contém uma solução melhor, pois esta falha em não-minúsculas s2. Não falando sobre detalhes como esse, este não é compilado e, se o fizer, retornará uma string.
Maaartinus
3

Aqui estão alguns compatíveis com Unicode que você pode criar se você usar o ICU4j. Eu acho que "ignorar maiúsculas e minúsculas" é questionável para os nomes dos métodos, porque, embora as comparações de força primária ignorem maiúsculas e minúsculas, elas são descritas como as especificidades dependentes da localidade. Mas espero que seja dependente da localidade da maneira que o usuário esperaria.

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
fonte
3

Fiz um teste para encontrar uma correspondência que não diferencia maiúsculas de minúsculas de uma string. Eu tenho um vetor de 150.000 objetos, todos com uma String como um campo e queria encontrar o subconjunto que correspondesse a uma string. Eu tentei três métodos:

  1. Converter tudo em minúsculas

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

    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 a correspondência: 182 msegs

  • Correspondências de sequência: 278 msegs

  • Expressão regular: 65 ms

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

Jan Newmarch
fonte
É bom que você coloque os resultados de tempo. Todo mundo diz o quão lento é o regex, mas, na realidade, é muito rápido se você precisar compilar o regex apenas uma vez.
woot 18/02
1

Existe uma maneira concisa e simples, usando o sinalizador regex (sem distinção entre maiúsculas e minúsculas {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
fonte
0

Não sei qual é a sua pergunta principal, mas sim, .contains diferencia maiúsculas de minúsculas.

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

Basicamente, é um método que usa duas strings. Supõe-se que seja uma versão que não diferencia maiúsculas de minúsculas de contains (). Ao usar o método contains, você deseja ver se uma sequência está contida na outra.

Esse método pega a string "sub" e verifica se é igual às substrings da string do contêiner que são iguais em comprimento ao "sub". Se você observar o forloop, verá que ele itera em substrings (que são o comprimento do "sub") sobre a string do contêiner.

Cada iteração verifica se a subcadeia de caracteres da string do contêiner está equalsIgnoreCaseno sub.

seth
fonte
basicamente, é um método que usa duas strings. supõe-se que seja uma versão que não diferencia maiúsculas de minúsculas de contains (). ao usar o método contains, você deseja ver se uma string está contida na outra. esse método pega a string que é "sub" e verifica se é igual às sub-strings da string do contêiner, que são iguais em comprimento ao "sub". se você olhar para o loop for, verá que ele itera nas sub-strings (que são o comprimento do "sub") sobre a string do contêiner. cada iteração verifica se a sub-cadeia da cadeia de contêineres é igual ou menor que a sub-cadeia.
seth
@ Você provavelmente deve adicionar isso à sua resposta.
O cara com o chapéu
2
Este é o método mais lento de todos os tempos ... e também falha no alemão.
Maaartinus
0

Se você precisar pesquisar uma sequência ASCII em outra sequência ASCII, como uma URL , encontrará minha solução melhor. Eu testei o método do icza e o meu pela velocidade e aqui estão os resultados:

  • O caso 1 levou 2788 ms - regionMatches
  • O 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));
}
Revertron
fonte
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
fonte
Obrigado por este trecho de código, que pode fornecer ajuda limitada a curto prazo. Uma explicação adequada melhoraria bastante seu valor a longo prazo, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com outras perguntas semelhantes. Por favor edite sua resposta para adicionar alguma explicação, incluindo as suposições que você fez.
Toby Speight
0
"AbCd".toLowerCase().contains("abcD".toLowerCase())
Takhir Atamuratov
fonte
2
Você pode melhorar sua resposta explicando como seu código resolve o problema?
Isuka
11
Essa resposta já foi sugerida em muitas das outras respostas mais detalhadas a essa pergunta que outras pessoas forneceram. Acho que essa resposta não serve para nada aqui.
DaveyDaveDave
0

Podemos usar stream com anyMatch e contém 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
fonte
0

ou você pode usar uma abordagem simples e apenas converter o caso da string em caso de substring e, em seguida, use o método contains.

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

Você poderia simplesmente fazer algo assim:

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