Daha Hızlı Bir Uygulama: Yararlanma String.regionMatches()
Normal ifade kullanmak nispeten yavaş olabilir. Sadece bir durumda kontrol etmek isteyip istemediğiniz önemli değildir. Ancak binlerce veya yüz binlerce dizeden oluşan bir diziniz veya koleksiyonunuz varsa, işler oldukça yavaşlayabilir.
Aşağıda sunulan çözüm ne düzenli ifadeler ne de kullanmaktadır toLowerCase()
(bu da yavaştır çünkü başka bir karakter dizisi oluşturur ve bunları çekten sonra atar).
Çözüm bilinmeyen String.regionMatches () yöntemi üzerine kuruludur . 2 String
bölgenin eşleşip eşleşmediğini kontrol eder , ancak önemli olan kullanışlı bir ignoreCase
parametreyle aşırı yüke sahip olmasıdır .
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;
}
Hız analizi
Bu hız analizi, roket bilimi anlamına gelmez, sadece farklı yöntemlerin ne kadar hızlı olduğuna dair kaba bir tablo.
5 yöntemi karşılaştırıyorum.
- Bizim containsIgnoreCase () yöntemi.
- Her iki dizeyi de küçük harfe ve aramaya dönüştürerek
String.contains()
.
- Kaynak dizgisini küçük harfe dönüştürerek ve
String.contains()
önbelleğe alınmış, alt kasalı alt dize ile çağrı yaparak. Bu çözüm zaten bir esnek alt dizeyi test ettiği için esnek değildir.
- Normal ifadeyi kullanma (kabul edilen cevap
Pattern.compile().matcher().find()
...)
- Düzenli ifade kullanmak, ancak önceden oluşturulmuş ve önbelleğe alınmış olarak
Pattern
. Bu çözüm zaten önceden tanımlanmış bir alt dizeyi test ettiği için esnek değildir.
Sonuçlar (yöntemi 10 milyon kez çağırarak):
- Bizim yöntemimiz: 670 ms
- 2x toLowerCase () ve şunu içerir (): 2829 ms
- 1x toLowerCase () ve önbelleğe alınmış alt dize içeren () içerir: 2446 ms
- Normal ifade: 7180 ms
- Önbelleğe alınmış normal ifade
Pattern
: 1845 ms
Bir tablodaki sonuçlar:
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
Bizim yöntemimiz, küçük harflere ve kullanıma göre 4 kat daha hızlıcontains()
, normal ifadeleri kullanmaya kıyasla 10 kat daha hızlı ve önbelleğe alınmış olsa bile 3 kat daha hızlıPattern
(ve keyfi bir alt dizeyi denetleme esnekliğini kaybetme).
Analiz Test Kodu
Analizin nasıl yapıldığıyla ilgileniyorsanız, çalıştırılabilir uygulamanın tamamı şöyledir:
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");
}
}