¿Cómo verificar si una Cadena contiene otra Cadena en mayúsculas y minúsculas en Java?

386

Digamos que tengo dos cuerdas

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

Quiero realizar una comprobación de devolución que s2está contenida dentro s1. Puedo hacer esto con:

return s1.contains(s2);

Estoy bastante seguro de que contains()distingue entre mayúsculas y minúsculas, sin embargo, no puedo determinar esto con seguridad leyendo la documentación. Si es así, supongo que mi mejor método sería algo como:

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

Dejando de lado todo esto, ¿hay otra manera (posiblemente mejor) de lograr esto sin preocuparse por la sensibilidad a mayúsculas y minúsculas?

Aaron
fuente
DrJava sería una forma extremadamente fácil de probar esto cuando la documentación le falla. Simplemente escriba un par de casos de prueba en su ventana de Interacciones, y debería averiguarlo.
EfForEffort
17
Creo que has respondido tu propia pregunta. No creo que ninguna de las soluciones a continuación sea mejor que esta. Pero definitivamente son más lentos.
Nikolay Dimitrov
77
Su solución es más simple que cualquiera de las respuestas
LobsterMan el
2
La respuesta que yo y muchos aquí estamos buscando está en su pregunta.
Lalit Fauzdar
1
Su ejemplo es la forma más simple, legible y probablemente la mejor manera de hacerlo, mejor que cualquiera de las respuestas que estoy viendo.
user1258361

Respuestas:

320

Sí, contiene mayúsculas y minúsculas. Puede usar java.util.regex.Pattern con el indicador CASE_INSENSITIVE para la coincidencia entre mayúsculas y minúsculas:

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

EDITAR: si s2 contiene caracteres especiales regex (de los cuales hay muchos), es importante citarlo primero. He corregido mi respuesta, ya que es la primera que verán las personas, pero vote por la de Matt Quail desde que señaló esto.

Dave L.
fuente
23
Como se indica en la documentación Pattern.CASE_INSENSITIVE, esto funciona solo para caracteres ASCII (es decir, "Ä" no coincidirá con "ä"). Uno necesita especificar adicionalmente la UNICODE_CASEbandera para lograr eso.
Philipp Wendler
72
¿Este enfoque utiliza Patternmás rendimiento que s1.toLowerCase().contains(s2.toLowerCase())?
Rajat Gupta
66
@ user01 Realicé un análisis de velocidad. Vea mi respuesta para los resultados (también mostré una solución más rápida): stackoverflow.com/a/25379180/1705598
icza
10
Me aclararía más qué pasaría si tuviéramos mejores nombres de variables:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
John Bowers
55
La corrección de @ user01 viene antes del rendimiento, y el uso de toLowerCase dará resultados potencialmente incorrectos (por ejemplo, al comparar cierto texto griego que contiene la letra Sigma, que tiene dos formas en minúscula para la misma forma en mayúscula).
Klitos Kyriacou
267

Un problema con la respuesta de Dave L. es cuando s2 contiene marcas de expresiones regulares como \d, etc.

Desea llamar a Pattern.quote () en s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Codorniz mate
fuente
1
Buena captura Matt. Tengo curiosidad por saber qué método es más eficiente: la letra minúscula o su solución de patrón. ¿El uso de un patrón no es menos eficiente para una comparación única, sino más eficiente para comparaciones múltiples?
Aaron
41
El método .toLowerCase (). Contiene () probablemente será más rápido en la mayoría de los casos. Probablemente también preferiría ese estilo para menor complejidad.
Matt Quail
3
@AaronFerguson Sí, de hecho, toLowerCase().contains()es más rápido.
Realicé
2
@MattQuail no tiene sentido que sea más rápido si puede ser incorrecto. Por ejemplo, la sigma en mayúscula griega tiene dos formas en minúsculas (dependiendo de si viene al final de una palabra o no) y cuando intentas hacer una coincidencia de subcadena que no distingue entre mayúsculas y minúsculas, donde la subcadena termina con una sigma, podrías obtener incorrectamente resultados.
Klitos Kyriacou
Creo que deberíamos agregar Pattern.UNICODE_CASEbandera también. ¿Podría por favor confirmar esto?
Thariq Nugrohotomo
160

Puedes usar

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

La biblioteca Apache Commons es muy útil para este tipo de cosas. Y esta en particular puede ser mejor que las expresiones regulares, ya que la expresión regular siempre es costosa en términos de rendimiento.

muhamadto
fuente
1
¿Alguien sabe si esto respeta la configuración regional?
Charles Wood
12
@CharlesWood Se delega a String.regionMatches, que utiliza conversiones de caracteres, por lo que no. Por otra parte, containsIgnoreCase("ß", "ss")devuelve -1, lo que es malo en todos los sitios (los alemanes "s" afilados capitaliza a "ss".
maaartinus
¿Cuál sería la forma correcta de comparar palabras alemanas entonces? Parece que es un idioma que complica todas las formas de comparar cadenas: P
chomp
1
Por cierto: el idioma alemán se amplió oficialmente con una ß mayúscula en 2017: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . En los teclados alemanes, escriba Shift + Alt Gr + ß -> prueba: ẞ 😁
Kawu
119

Una implementación más rápida: utilización String.regionMatches()

Usar regexp puede ser relativamente lento. (Ser lento) no importa si solo quieres verificar en un caso. Pero si tiene una matriz o una colección de miles o cientos de miles de cadenas, las cosas pueden volverse bastante lentas.

La solución presentada a continuación no usa expresiones regulares ni toLowerCase() (que también es lenta porque crea otras cadenas y las tira después de la comprobación).

La solución se basa en el método String.regionMatches () que parece ser desconocido. Comprueba si 2 Stringregiones coinciden, pero lo importante es que también tiene una sobrecarga con un 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álisis de velocidad

Este análisis de velocidad no significa ser ciencia de cohetes, solo una imagen aproximada de lo rápido que son los diferentes métodos.

Comparo 5 métodos.

  1. Nuestro contieneIgnoreCase () método .
  2. Al convertir ambas cadenas a minúsculas y llamar String.contains() .
  3. Al convertir la cadena de origen en minúsculas y llamar String.contains() con la subcadena pre-almacenada en caché y en minúsculas. Esta solución ya no es tan flexible porque prueba una subcadena predefinida.
  4. Usando expresión regular (la respuesta aceptada Pattern.compile().matcher().find()...)
  5. Usando expresiones regulares pero con pre-creado y almacenado en caché Pattern. Esta solución ya no es tan flexible porque prueba una subcadena predefinida.

Resultados (llamando al método 10 millones de veces):

  1. Nuestro método: 670 ms
  2. 2x toLowerCase () y contiene (): 2829 ms
  3. 1x toLowerCase () y contiene () con subcadena en caché: 2446 ms
  4. Regexp: 7180 ms
  5. Regexp con caché Pattern: 1845 ms

Resultados en una tabla:

                                            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

Nuestro método es 4 veces más rápido en comparación con las minúsculas y el uso contains(), 10 veces más rápido en comparación con el uso de expresiones regulares y también 3 veces más rápido incluso si Patternestá pre-almacenado en caché (y pierde la flexibilidad de verificar una subcadena arbitraria).


Código de prueba de análisis

Si está interesado en cómo se realizó el análisis, aquí está la aplicación completa ejecutable:

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
fuente
66
+1 pero tenga en cuenta que falla para ß(alemán S agudo; capitaliza a SS) y también para algunos otros caracteres (vea la fuente de String.regionMatches, que intenta ambas conversiones).
maaartinus
2
Siempre prueba las mismas cadenas, lo cual no es realmente una comparación justa. 'yo soy' siempre está en el medio, lo que podría o no marcar la diferencia para los diferentes métodos de búsqueda. Mejor sería generar cadenas aleatorias y también informar sobre la velocidad cuando una subcadena no está presente.
2
Eso parece muy parecido al método Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/…
alain.janinm
1
@ alain.janinm No veo las similitudes. Lo único que parece "cercano" StringUtils.containsIgnoreCase()es que tanto mi solución como la de Apache usan un regionMatches()método (en un ciclo), pero incluso eso no es lo mismo que llamo String.regionMatches()y las llamadas de Apache CharSequenceUtils.regionMatches().
icza
2
@icza CharSequenceUtils.regionMatchessolo llama en String.regionMatchesrealidad. De todos modos, mi punto era dar la información, que si alguien ya está usando StringUtils lib, simplemente puede llamarlo porque parece ser una forma eficiente como lo demuestra con su punto de referencia. Si no estuviera usando Apache lib, definitivamente usaría su método;)
alain.janinm
22

Una forma más simple de hacer esto (sin preocuparse por la coincidencia de patrones) sería convertir ambos Strings en minúsculas:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}
Phil
fuente
44
El caso de los caracteres depende del idioma, lo que significa que funcionará en su computadora pero fallará para el cliente :). ver el comentario de @Adriaan Koster.
kroiz
1
@kroiz, eso depende de dónde vino la cadena. La comparación de "foobar" y "FOO" siempre coincidirá, sin embargo, si está comparando información ingresada por el usuario o contenido específico del idioma, entonces tiene razón: un desarrollador debe ser cauteloso.
Phil
16

Sí, esto se puede lograr:

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 devolverá la cadena "¡VERDADERO!" ya que descubrió que tus personajes estaban contenidos.

Bilbo Baggins
fuente
12
Un gran inconveniente de usar toLowerCase () es que el resultado depende de la configuración regional actual. Ver: javapapers.com/core-java/…
Adriaan Koster
44
La pregunta en realidad contiene una mejor solución ya que esta falla para minúsculas s2. Sin hablar de detalles como este, este no se compila y, si lo hiciera, devolvería una cadena.
maaartinus
3

Aquí hay algunos compatibles con Unicode que puede hacer si tira de ICU4j. Supongo que "ignorar mayúsculas y minúsculas" es cuestionable para los nombres de métodos porque, aunque las comparaciones de fuerza primarias ignoran mayúsculas y minúsculas, se describen como los detalles que dependen de la configuración regional. Pero es de esperar que dependa de la ubicación de una manera que el usuario esperaría.

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

Hice una prueba para encontrar una coincidencia entre mayúsculas y minúsculas de una cadena. Tengo un Vector de 150,000 objetos, todos con una Cadena como un solo campo y quería encontrar el subconjunto que coincidía con una cadena. Probé tres métodos:

  1. Convertir todo a minúsculas

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

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

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

Los resultados del tiempo son:

  • Sin intento de coincidencia: 20 ms

  • Para reducir la coincidencia: 182 ms

  • Cadenas: 278 ms.

  • Expresión regular: 65 ms.

La expresión regular parece ser la más rápida para este caso de uso.

Jan Newmarch
fuente
Es bueno que pongas los resultados del tiempo. Todos dicen cuán lenta es la expresión regular, pero en realidad es muy rápida si solo tienes que compilar la expresión regular una vez.
woot
1

Hay una manera simple y concisa, utilizando la bandera regex (mayúsculas y 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
fuente
0

No estoy seguro de cuál es su pregunta principal aquí, pero sí, .contains distingue mayúsculas de minúsculas.

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

Básicamente, es un método que toma dos cadenas. Se supone que es una versión sin distinción entre mayúsculas y minúsculas de contiene (). Cuando se utiliza el método contiene, desea ver si una cadena está contenida en la otra.

Este método toma la cadena que es "sub" y comprueba si es igual a las subcadenas de la cadena de contenedor que tienen la misma longitud que la "sub". Si observa el forbucle, verá que itera en subcadenas (que son la longitud del "sub") sobre la cadena del contenedor.

Cada iteración verifica si la subcadena de la cadena del contenedor está equalsIgnoreCaseen la sub.

seth
fuente
Básicamente es un método que toma dos cadenas. se supone que es una versión que no distingue mayúsculas de minúsculas de contiene (). cuando se utiliza el método contiene, desea ver si una cadena está contenida en la otra. este método toma la cadena que es "sub" y comprueba si es igual a las subcadenas de la cadena del contenedor, que son de igual longitud que la "sub". si observa el bucle for, verá que itera en subcadenas (que son la longitud del "sub") sobre la cadena del contenedor. cada iteraciones verifica si la subcadena de la cadena del contenedor es igual a la subescritura.
seth
@ Probablemente deberías agregar eso a tu respuesta.
El chico con el sombrero
2
Este es el método más lento ... y también falla para el alemán.
maaartinus
0

Si tiene que buscar una cadena ASCII en otra cadena ASCII, como una URL , encontrará que mi solución es mejor. He probado el método de icza y el mío para la velocidad y aquí están los resultados:

  • El caso 1 tomó 2788 ms - regionMatches
  • El caso 2 tomó 1520 ms - mi

El 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
fuente
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
fuente
Gracias por este fragmento de código, que podría proporcionar una ayuda limitada a corto plazo. Una explicación adecuada mejoraría en gran medida su valor a largo plazo al mostrar por qué esta es una buena solución al problema y lo haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Toby Speight
0
"AbCd".toLowerCase().contains("abcD".toLowerCase())
Takhir Atamuratov
fuente
2
¿Puede mejorar su respuesta explicando cómo su código resuelve el problema?
Isuka
1
Esta respuesta ya ha sido sugerida en muchas de las otras respuestas más detalladas a esta pregunta que otros han proporcionado. No creo que esta respuesta tenga ningún propósito aquí.
DaveyDaveDave
0

Podemos usar stream con anyMatch y 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;
    }

}
Soudipta Dutta
fuente
0

o puede usar un enfoque simple y simplemente convertir el caso de la cadena en el caso de la subcadena y luego usar el método contiene.

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

Simplemente podrías hacer algo como esto:

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