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 String
regiões correspondem, mas o importante é que ele também tenha uma sobrecarga com um ignoreCase
parâ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.
- Nosso método containsIgnoreCase () .
- Convertendo as duas strings para minúsculas e chamadas
String.contains()
.
- 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.
- Usando expressão regular (a resposta aceita
Pattern.compile().matcher().find()
...)
- 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):
- Nosso método: 670 ms
- 2x paraLowerCase () e contém (): 2829 ms
- 1x toLowerCase () e contém () com substring em cache: 2446 ms
- Regexp: 7180 ms
- 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 Pattern
cache 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");
}
}