**本文正在参加「Java主题月 – Java Debug笔记活动」,详情查看 活动链接 **
问题:
我有两个字符串如下:
String s1 = "AbBaCca";
String s2 = "bac";
复制代码
我想要检查s2
包含了s1
可以这样写:
return s1.contains(s2);
复制代码
我很肯定contains()
方法是区分大小写的,我可以通过官方文档确定这一点。
基于这个原因我能想到解决问题最好的方式是这样:
return s1.toLowerCase().contains(s2.toLowerCase());
复制代码
除开这种方法,是否有其它(更好)的方式来实现不区分大小写匹配呢呢?
回答1(支持票数332):
是的,contains()
方法要区分大小写,你可以使用java.util.regex.Pattern
类的 CASE_INSENSITIVE
标记 来进行不区分大小写的匹配:
Pattern.compile(wantedStr, Pattern.CASE_INSENSITIVE).matcher(source).find();
复制代码
回答2(支持票数276):
当s2
包含了正则表达式标记例如\d
时回答1的答案就会有问题。
这种情况下应该对s2使用 Pattern.quote()
方法
Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
复制代码
回答3(支持票数180):
你可以使用:
org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");
复制代码
这个apache的工具类是很用的,而且性能可能比你写的正则表达式更好
回答4(支持票数129):
有一个更快的实现方式:利用String.regionMatches()
使用正则表达式可能会相对比较慢。如果你只想检查一种情况慢一点没有关系,
但是如果你的数组或集合有成千上万条字符串,那么匹配就会变得非常慢。
下面给出的解决方案没有使用正则表达式和toLowerCase()
方法(这样之所以慢是因为它生成了新的字符串,然后在检查完后就销毁掉)
这个解决方案是基于**[String.regionMatches()](http://docs.oracle.com/javase/8/docs/api/java/lang/String.html#regionMatches-boolean-int-java.lang.String-int-int-)
**这个看上去没见过的方法。它是用于检查两个字符串是否匹配,更重要的是这个方法有一个便于重载的ignoreCase
参数
public static boolean containsIgnoreCase(String src, String what) {
final int length = what.length();
if (length == 0)
return true; // 包含空字符串
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种方法:
1.自己写的**containsIgnoreCase()
方法**
2.将字符串转换成小写并调用String.contains()
方法
3.将源字符串转换成小写缓存起来,然后调用String.contains()
方法.这种解决方案已经不够灵活,因为它只能测试预先设定好的字符串
4.使用正则表达式(用这个Pattern.compile().matcher().find()
..)
5.提前创建正则表达式和缓存Pattern
.这种解决方案已经不够灵活,因为它只能测试预先设定好的字符串
速度测试结果(每个方法均调用1000万次):
1.第1种方法:670ms
2.第2种方法:2x toLowerCase() and contains(): 2829 ms
3.第3种方法:1x toLowerCase() and contains() with cached substring: 2446 ms
4.第4种方法:7180ms
5.第5种方法:Regexp with cached Pattern:
1845ms
做成一个表格:
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
复制代码
第1种方法的速度是第2、3种方法的四倍,第4种方法的十倍,第5种方法的3倍
用于速度分析的测试代码如下:
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 = "https://juejin.cn/post/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");
}
}
复制代码