说我有两个字符串,
1 2
| String s1 ="AbBaCca";
String s2 ="bac"; |
我想执行一个检查,返回s2包含在s1中。 我可以这样做:
我很确定contains()区分大小写,但是我无法通过阅读文档来确定这一点。 如果是,那么我想我最好的方法是这样的:
1
| return s1.toLowerCase().contains(s2.toLowerCase()); |
除此之外,还有另一种(可能更好的)方法来实现这一目标而不关心区分大小写吗?
是的,包含区分大小写。您可以将java.util.regex.Pattern与CASE_INSENSITIVE标志一起用于不区分大小写的匹配:
1
| Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find(); |
编辑:如果s2包含正则表达式特殊字符(其中有很多),首先引用它是很重要的。我已经纠正了我的答案,因为这是人们会看到的第一个答案,但是自从他指出这一点后就投票给Matt Quail。
Dave L.答案的一个问题是当s2包含正则表达式标记,例如\d等。
你想在s2上调用Pattern.quote():
1
| Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find(); |
您可以使用
1
| org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca","bac"); |
Apache Commons库对于这类事情非常有用。而且这个特定的可能比正则表达式更好,因为正则表达式在性能方面总是很昂贵。
更快的实现:利用
String.regionMatches()
使用regexp可能会相对较慢。如果您只是想检查一个案例,那么(缓慢)并不重要。但是如果你有一个数组或数千或数十万个字符串的集合,那么事情就会变得非常缓慢。
下面介绍的解决方案不使用正则表达式也不使用toLowerCase()(这也很慢,因为它会创建另一个字符串,并在检查后将它们抛出)。
该解决方案基于String.regionMatches()方法构建,该方法似乎未知。它检查2 String个区域是否匹配,但重要的是它还有一个带有方便的ignoreCase参数的重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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种方法。
我们的containsIgnoreCase()方法。
通过将两个字符串转换为小写并调用String.contains()。
通过将源字符串转换为小写字母并使用预缓存的低级子字符串调用String.contains()。这个解决方案已经不那么灵活,因为它测试了一个预先定义的子字符串。
使用正则表达式(接受的答案Pattern.compile().matcher().find() ...)
使用正则表达式,但使用预先创建和缓存的Pattern。此解决方案已经不那么灵活,因为它测试预定义的子字符串。
结果(通过调用方法1000万次):
我们的方法:670毫秒
2x toLowerCase()并包含():2829 ms
1x toLowerCase()和contains(),缓存的子字符串:2446 ms
Regexp:7180毫秒
带有缓存Pattern的正则表达式:1845毫秒
结果表:
1 2 3 4 5 6 7 8
| 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 |
与使用contains()相比,我们的方法快了4倍,与使用正则表达式相比,快了10倍,即使Pattern预先缓存(并且无法检查任意子字符串的灵活性)也快3倍。
分析测试代码
如果您对分析的执行方式感兴趣,请参阅完整的可运行应用程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| 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");
}
} |
一个更简单的方法(不用担心模式匹配)将两个String转换为小写:
1 2 3 4 5
| String foobar ="fooBar";
String bar ="FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
System.out.println("It's a match!");
} |
是的,这是可以实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| String s1 ="abBaCca";
String s2 ="bac";
String s1Lower = s1;
//s1Lower is exact same string, now convert it to lowercase, I left the s1 intact for print purposes if needed
s1Lower = s1Lower.toLowerCase();
String trueStatement ="FALSE!";
if (s1Lower.contains(s2)) {
//THIS statement will be TRUE
trueStatement ="TRUE!"
}
return trueStatement; |
此代码将返回字符串"TRUE!"因为它发现你的角色被包含了。
您可以使用正则表达式,它可以工作:
1
| boolean found = s1.matches("(?i).*" + s2+".*"); |
如果你拉入ICU4j,这里有一些你可以使用的非常友好的。我猜"忽略大小写"对于方法名称是有问题的,因为虽然主要强度比较确实忽略了大小写,但它被描述为特定于区域设置的。但它有望以一种用户期望的方式依赖于语言环境。
1 2 3 4 5 6 7 8 9
| public static boolean containsIgnoreCase(String haystack, String needle) {
return indexOfIgnoreCase(haystack, needle) >= 0;
}
public static int indexOfIgnoreCase(String haystack, String needle) {
StringSearch stringSearch = new StringSearch(needle, haystack);
stringSearch.getCollator().setStrength(Collator.PRIMARY);
return stringSearch.first();
} |
我做了一个测试,发现一个字符串不区分大小写的匹配。我有一个150,000个对象的Vector,所有对象都有一个字符串作为一个字段,并希望找到匹配字符串的子集。我尝试了三种方法:
将全部转换为小写
1 2 3 4 5
| for (SongInformation song: songs) {
if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
...
}
} |
使用String matches()方法
1 2 3 4 5
| for (SongInformation song: songs) {
if (song.artist.matches("(?i).*" + pattern +".*")) {
...
}
} |
使用正则表达式
1 2 3 4 5 6 7 8
| Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher("");
for (SongInformation song: songs) {
m.reset(song.artist);
if (m.find()) {
...
}
} |
时间结果是:
-
没有尝试匹配:20毫秒
-
降低比赛:182毫秒
-
字符串匹配:278毫秒
-
正则表达式:65毫秒
对于此用例,正则表达式看起来是最快的。
1
| "AbCd".toLowerCase().contains("abcD".toLowerCase()) |
我不确定你的主要问题是什么,但是,.contains是区分大小写的。
或者您可以使用一种简单的方法,只需将字符串的大小写转换为substring的大小写,然后使用contains方法。
如果您必须在另一个ASCII字符串(例如URL)中搜索ASCII字符串,您会发现我的解决方案更好。我已经测试了icza的方法和我的速度,以下是结果:
-
案例1耗时2788毫秒 - regionMatches
-
案例2耗时1520毫秒 - 我的
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static String lowerCaseAscii(String s) {
if (s == null)
return null;
int len = s.length();
char[] buf = new char[len];
s.getChars(0, len, buf, 0);
for (int i=0; i<len; i++) {
if (buf[i] >= 'A' && buf[i] <= 'Z')
buf[i] += 0x20;
}
return new String(buf);
}
public static boolean containsIgnoreCaseAscii(String str, String searchStr) {
return StringUtils.contains(lowerCaseAscii(str), lowerCaseAscii(searchStr));
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.text.Normalizer;
import org.apache.commons.lang3.StringUtils;
public class ContainsIgnoreCase {
public static void main(String[] args) {
String in =" Annulée";
String key ="annulee";
// 100% java
if (Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]","").toLowerCase().contains(key)) {
System.out.println("OK");
} else {
System.out.println("KO");
}
// use commons.lang lib
if (StringUtils.containsIgnoreCase(Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]",""), key)) {
System.out.println("OK");
} else {
System.out.println("KO");
}
}
} |
我们可以使用带有anyMatch的流和包含Java 8的流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Test2 {
public static void main(String[] args) {
String a ="Gina Gini Protijayi Soudipta";
String b ="Gini";
System.out.println(WordPresentOrNot(a, b));
}// main
private static boolean WordPresentOrNot(String a, String b) {
//contains is case sensitive. That's why change it to upper or lower case. Then check
// Here we are using stream with anyMatch
boolean match = Arrays.stream(a.toLowerCase().split("")).anyMatch(b.toLowerCase()::contains);
return match;
}
} |
有一个简单的简洁方法,使用正则表达式标志(不区分大小写{i}):
1 2 3 4 5 6 7 8
| String s1 ="hello abc efg";
String s2 ="ABC";
s1.matches(".*(?i)"+s2+".*");
/*
* .* denotes every character except line break
* (?i) denotes case insensitivity flag enabled for s2 (String)
* */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| String container =" Case SeNsitive";
String sub ="sen";
if (rcontains(container, sub)) {
System.out.println("no case");
}
public static Boolean rcontains(String container, String sub) {
Boolean b = false;
for (int a = 0; a < container.length() - sub.length() + 1; a++) {
//System.out.println(sub +" to" + container.substring(a, a+sub.length()));
if (sub.equalsIgnoreCase(container.substring(a, a + sub.length()))) {
b = true;
}
}
return b;
} |
基本上,它是一种采用两个字符串的方法。它应该是contains()的不区分大小写的版本。使用contains方法时,您希望查看另一个字符串是否包含一个字符串。
此方法接受"sub"字符串,并检查它是否等于容器字符串的子字符串,其长度与"sub"相等。如果查看for循环,您将看到它在容器字符串上的子字符串(即"sub"的长度)中进行迭代。
每次迭代检查以查看容器字符串的子字符串是否为equalsIgnoreCase。
你可以简单地做这样的事情:
1 2 3 4
| String s1 ="AbBaCca";
String s2 ="bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2); |
1 2
| String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find()); |