Eine schnellere Umsetzung: Die Nutzung von String.regionMatches()
Die Verwendung von regexp kann relativ langsam sein. Wenn Sie nur in einem Fall prüfen wollen, macht das nichts aus. Aber wenn Sie ein Array oder eine Sammlung von Tausenden oder Hunderttausenden von Zeichenfolgen haben, kann es ziemlich langsam werden.
Die unten vorgestellte Lösung verwendet weder reguläre Ausdrücke noch toLowerCase()
(was ebenfalls langsam ist, da es weitere Zeichenketten erstellt und diese nach der Prüfung einfach wegwirft).
Die Lösung baut auf dem String.regionMatches() Methode, die unbekannt zu sein scheint. Sie prüft, ob 2 String
Regionen übereinstimmen, aber was wichtig ist, ist, dass es auch eine Überlastung mit einer praktischen ignoreCase
Parameter.
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 der Geschwindigkeit
Diese Geschwindigkeitsanalyse soll keine Raketenwissenschaft sein, sondern nur ein grobes Bild davon vermitteln, wie schnell die verschiedenen Methoden sind.
Ich vergleiche 5 Methoden.
- Unser enthältKleinschreibung ignorieren() Methode.
- Durch Konvertierung beider Zeichenketten in Kleinbuchstaben und Aufruf von
String.contains()
.
- Durch Konvertierung der Quellzeichenkette in Kleinbuchstaben und Aufruf von
String.contains()
mit der vorab zwischengespeicherten, kleingeschriebenen Teilzeichenkette. Diese Lösung ist schon deshalb nicht so flexibel, weil sie eine vordefinierte Teilzeichenkette testet.
- Mit regulärem Ausdruck (die akzeptierte Antwort
Pattern.compile().matcher().find()
...)
- Verwendung regulärer Ausdrücke, aber mit vorab erstellten und zwischengespeicherten
Pattern
. Diese Lösung ist schon deshalb nicht so flexibel, weil sie einen vordefinierten Teilstring testet.
Ergebnisse (durch 10 Millionen Maliges Aufrufen der Methode):
- Unsere Methode: 670 ms
- 2x toLowerCase() und contains(): 2829 ms
- 1x toLowerCase() und contains() mit zwischengespeicherter Teilzeichenkette: 2446 ms
- Regexp: 7180 ms
- Regexp mit zwischengespeicherten
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 im Vergleich zu Kleinbuchstaben und der Verwendung von contains()
, 10x schneller im Vergleich zur Verwendung regulärer Ausdrücke und auch 3x schneller auch wenn die Pattern
zwischengespeichert wird (und die Flexibilität der Prüfung auf eine beliebige Teilzeichenkette verloren geht).
Analyse Test Code
Wenn es Sie interessiert, wie die Analyse durchgeführt wurde, finden Sie hier die vollständige lauffähige 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");
}
}