Eine schnellere Implementierung: Nutzen String.regionMatches()
Die Verwendung von Regexp kann relativ langsam sein. Es ist (langsam) egal, ob Sie nur in einem Fall überprüfen möchten. Aber wenn Sie ein Array oder eine Sammlung von Tausenden oder Hunderttausenden von Zeichenfolgen haben, kann es ziemlich langsam werden.
Die unten dargestellte Lösung verwendet weder reguläre Ausdrücke noch toLowerCase()
(was auch langsam ist, weil es andere Zeichenfolgen erstellt und diese nach der Prüfung einfach wegwirft).
Die Lösung baut auf der String.regionMatches () -Methode auf, die unbekannt zu sein scheint. Es wird geprüft, ob zwei String
Regionen übereinstimmen. Wichtig ist jedoch, dass es auch eine Überlastung mit einem praktischen ignoreCase
Parameter gibt.
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;
}
Geschwindigkeitsanalyse
Diese Geschwindigkeitsanalyse bedeutet nicht, Raketenwissenschaft zu sein, sondern nur ein grobes Bild davon, wie schnell die verschiedenen Methoden sind.
Ich vergleiche 5 Methoden.
- Unsere Methode includesIgnoreCase () .
- Durch Konvertieren beider Zeichenfolgen in Kleinbuchstaben und Aufrufen
String.contains()
.
- Durch Konvertieren der Quellzeichenfolge in Kleinbuchstaben und Aufrufen
String.contains()
mit der vorab zwischengespeicherten Teilzeichenfolge in Kleinbuchstaben. Diese Lösung ist bereits nicht so flexibel, da sie einen vordefinierten Teilstring testet.
- Mit regulären Ausdrücken (die akzeptierte Antwort
Pattern.compile().matcher().find()
...)
- Mit regulären Ausdrücken, aber mit vorgefertigten und zwischengespeicherten
Pattern
. Diese Lösung ist bereits nicht so flexibel, da sie einen vordefinierten Teilstring testet.
Ergebnisse (durch 10 Millionen Aufruf der Methode):
- Unsere Methode: 670 ms
- 2x toLowerCase () und enthält (): 2829 ms
- 1x toLowerCase () und enthält () mit zwischengespeicherter Teilzeichenfolge: 2446 ms
- Regexp: 7180 ms
- Regexp mit zwischengespeichertem
Pattern
: 1845 ms
Ergebnisse in einer Tabelle:
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
Unsere Methode ist 4x schneller als die Verwendung von Kleinbuchstaben und die Verwendung contains()
, 10x schneller als die Verwendung regulärer Ausdrücke und 3x schneller, selbst wenn die Pattern
vorab zwischengespeichert ist (und die Flexibilität bei der Suche nach einem beliebigen Teilstring verliert).
Analyse-Testcode
Wenn Sie interessiert sind, wie die Analyse durchgeführt wurde, finden Sie hier die vollständige ausführbare Anwendung:
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");
}
}