Implementasi Lebih Cepat: Memanfaatkan String.regionMatches()
Menggunakan regexp bisa relatif lambat. Itu (lambat) tidak masalah jika Anda hanya ingin memeriksa dalam satu kasing. Tetapi jika Anda memiliki array atau koleksi ribuan atau ratusan ribu string, semuanya bisa menjadi sangat lambat.
Solusi yang disajikan di bawah ini tidak menggunakan ekspresi reguler atau toLowerCase()
(yang juga lambat karena itu menciptakan string lain dan hanya membuangnya setelah cek).
Solusinya dibangun di atas metode String.regionMatches () yang tampaknya tidak diketahui. Ia memeriksa apakah 2 String
wilayah cocok, tetapi yang penting adalah bahwa ia juga memiliki kelebihan dengan ignoreCase
parameter praktis .
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;
}
Analisis Kecepatan
Analisis kecepatan ini tidak berarti menjadi ilmu roket, hanya gambaran kasar tentang seberapa cepat berbagai metode tersebut.
Saya membandingkan 5 metode.
- Kami containsIgnoreCase () metode.
- Dengan mengkonversi kedua string ke huruf kecil dan panggilan
String.contains()
.
- Dengan mengonversi string sumber ke huruf kecil dan memanggil
String.contains()
dengan substring yang lebih dulu di-cache dan lebih rendah-huruf. Solusi ini sudah tidak sefleksibel karena menguji substring yang telah ditentukan sebelumnya.
- Menggunakan ekspresi reguler (jawaban yang diterima
Pattern.compile().matcher().find()
...)
- Menggunakan ekspresi reguler tetapi dengan pra-dibuat dan di-cache
Pattern
. Solusi ini sudah tidak sefleksibel karena menguji substring yang telah ditentukan.
Hasil (dengan memanggil metode 10 juta kali):
- Metode kami: 670 ms
- 2x toLowerCase () dan berisi (): 2829 ms
- 1x toLowerCase () dan berisi () dengan substring di-cache: 2446 ms
- Regexp: 7180 ms
- Regexp dengan cache
Pattern
: 1845 ms
Hasil dalam tabel:
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
Metode kami 4x lebih cepat dibandingkan dengan menggunakan dan menggunakan lebih rendah contains()
, 10x lebih cepat dibandingkan dengan menggunakan ekspresi reguler dan juga 3x lebih cepat bahkan jika Pattern
pra-cache (dan kehilangan fleksibilitas memeriksa untuk substring sewenang-wenang).
Kode Uji Analisis
Jika Anda tertarik bagaimana analisis dilakukan, berikut ini adalah aplikasi runnable yang lengkap:
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");
}
}