A Более быстрое внедрение: использование String.regionMatches()
Использование регулярных выражений может быть относительно медленным. Это (медленно) не имеет значения, если вы просто хотите проверить в одном случае. Но если у вас есть массив или набор из тысяч или сотен тысяч строк, все может стать довольно медленным.
Представленное ниже решение не использует ни регулярные выражения, ни toLowerCase()
(что также является медленным, поскольку создает другие строки и просто выбрасывает их после проверки).
Решение основано на методе String.regionMatches () , который кажется неизвестным. Он проверяет, совпадают ли 2 String
региона, но важно то, что он также имеет перегрузку с удобным параметром ignoreCase
.
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;
}
Анализ скорости
Этот анализ скорости означает не ракетостроение, а лишь приблизительное представление о том, насколько быстрыми являются различные методы.
Я сравниваю 5 методов.
- Наш метод содержит методIgnoreCase () .
- Путем преобразования обеих строк в нижний регистр и вызова
String.contains()
.
- Путем преобразования исходной строки в нижний регистр и вызова
String.contains()
с предварительно кэшированной подстрокой в нижнем регистре. Это решение уже не так гибко, потому что оно тестирует предстроку подстроки.
- Использование регулярного выражения (принятый ответ
Pattern.compile().matcher().find()
...)
- Использование регулярного выражения, но с предварительно созданным и кэшированным
Pattern
. Это решение уже не так гибко, потому что оно тестирует предопределенную подстроку.
Результаты (вызывая метод 10 миллионов раз):
- Наш метод: 670 мс
- 2x toLowerCase () и содержит (): 2829 мс
- 1x toLowerCase () и содержит () с кэшированной подстрокой: 2446 мс
- Регулярное выражение: 7180 мс
- Регулярное выражение с кэшированием
Pattern
: 1845 мс
Результаты в таблице:
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
Наш метод * в 1060 * 4 раза быстрее по сравнению с нижним регистром и использованием contains()
, 10x быстрее по сравнению с использованием регулярных выражений и также в 3 раза быстрее даже если 1067 * предварительно кэшируется (и теряет гибкость проверки произвольной подстроки).
Код аналитического теста
Если вам интересно, как был проведен анализ, вот полное приложение, которое можно запустить:
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");
}
}