Une mise en œuvre plus rapide: utilisation String.regionMatches()
L'utilisation d'expressions régulières peut être relativement lente. Cela (être lent) n'a pas d'importance 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 assez lentes.
La solution présentée ci-dessous n'utilise pas d'expressions régulières ni toLowerCase()
(ce qui est également lent car il crée une autre chaîne et la jette juste après la vérification).
La solution s'appuie sur la méthode String.regionMatches () qui semble inconnue. Il vérifie si 2 String
régions correspondent, mais ce qui est important, c'est qu'il a également une surcharge avec un ignoreCase
paramètre pratique .
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 une science de fusée, juste une image approximative de la rapidité des différentes méthodes.
Je compare 5 méthodes.
- Notre méthode containsIgnoreCase () .
- En convertissant les deux chaînes en minuscules et en appelant
String.contains()
.
- En convertissant la chaîne source en minuscules et appelez
String.contains()
avec la sous-chaîne pré-mise en cache et en minuscules. Cette solution n'est déjà pas aussi flexible car elle teste une sous-chaîne prédéfinie.
- Utiliser l'expression régulière (la réponse acceptée
Pattern.compile().matcher().find()
...)
- Utilisation d'expressions régulières mais avec pré-création et mise en cache
Pattern
. 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):
- Notre méthode: 670 ms
- 2x toLowerCase () et contient (): 2829 ms
- 1x toLowerCase () et contient () avec sous-chaîne en cache: 2446 ms
- Expression rationnelle: 7180 ms
- Expression régulière avec mise 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 4 fois plus rapide par rapport à l'utilisation de minuscules et à l'utilisation contains()
, 10 fois plus rapide que l' utilisation d'expressions régulières et également 3 fois plus rapide même si le Pattern
est pré-mis en cache (et perd la flexibilité de vérifier une sous-chaîne arbitraire).
Code de test d'analyse
Si vous êtes intéressé par la façon dont l'analyse a été effectuée, voici l'application exécutable complète:
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");
}
}