Szybsza implementacja: wykorzystanie String.regionMatches()
Korzystanie z wyrażenia regularnego może być stosunkowo wolne. To (powolne) nie ma znaczenia, jeśli chcesz tylko sprawdzić w jednym przypadku. Ale jeśli masz tablicę lub kolekcję tysięcy lub setek tysięcy ciągów, rzeczy mogą być dość powolne.
Przedstawione poniżej rozwiązanie nie używa wyrażeń regularnych ani toLowerCase()
(co jest również powolne, ponieważ tworzy kolejne ciągi znaków i po prostu je wyrzuca po sprawdzeniu).
Rozwiązanie opiera się na metodzie String.regionMatches () , która wydaje się nieznana. Sprawdza, czy 2 String
regiony pasują do siebie, ale ważne jest to, że ma również przeciążenie przydatnym ignoreCase
parametrem.
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;
}
Analiza prędkości
Ta analiza prędkości nie oznacza nauki o rakietach, tylko przybliżony obraz szybkości różnych metod.
Porównuję 5 metod.
- Nasza metoda zawieraIgnoreCase () .
- Konwertując oba ciągi znaków na małe litery i wywołanie
String.contains()
.
- Konwertując łańcuch źródłowy na małe litery i wywołuj
String.contains()
z wstępnie buforowanym podciągiem, małymi literami. To rozwiązanie nie jest już tak elastyczne, ponieważ testuje podciąg predefiend.
- Używanie wyrażenia regularnego (zaakceptowana odpowiedź
Pattern.compile().matcher().find()
...)
- Używanie wyrażenia regularnego, ale z wcześniej utworzonym i buforowanym
Pattern
. To rozwiązanie nie jest już tak elastyczne, ponieważ testuje predefiniowany podciąg.
Wyniki (wywołując metodę 10 milionów razy):
- Nasza metoda: 670 ms
- 2x toLowerCase () i zawiera (): 2829 ms
- 1x toLowerCase () i zawiera () z buforowanym podciągiem: 2446 ms
- Regexp: 7180 ms
- Regexp z pamięcią podręczną
Pattern
: 1845 ms
Wyniki w tabeli:
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
Nasza metoda jest czterokrotnie szybsza w porównaniu do mniejszych liter i używania contains()
, 10 razy szybsza w porównaniu do używania wyrażeń regularnych, a także 3 razy szybsza, nawet jeśli Pattern
jest wstępnie buforowana (i traci elastyczność sprawdzania dowolnego podciągu).
Kod testu analitycznego
Jeśli interesuje Cię sposób przeprowadzenia analizy, oto kompletna aplikacja do uruchomienia:
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");
}
}