如何检查一个字符串是否包含另一个字符串在Java中不区分大小写的方式?

假设我有两个字符串,

String s1 = "AbBaCca";
String s2 = "bac";

我想执行一个检查,返回s2包含在s1中。我可以这样做:

return s1.contains(s2);

我非常确定contains()是区分大小写的,但是我不能从阅读文档中确定这一点。如果是的话,我想我最好的方法是:

return s1.toLowerCase().contains(s2.toLowerCase());

撇开所有这些不谈,有没有另一种(可能更好的)方法可以在不考虑大小写敏感性的情况下完成这个任务?

498384 次浏览

是的,contains区分大小写。你可以使用带有CASE_INSENSITIVE标志的java.util.regex.Pattern进行不区分大小写的匹配:

Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find();

编辑:如果s2包含正则表达式特殊字符(其中有很多),首先引用它是很重要的。我已经更正了我的答案,因为这是人们看到的第一个答案,但是给马特·奎尔的答案投票,因为他指出了这一点。

我不确定你的主要问题是什么,但是是的,.contains是区分大小写的。

答案是Dave L。的一个问题是当s2包含诸如\d等正则表达式标记时。

你想在s2上调用Pattern.quote():

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();

是的,这是可以实现的:

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!”,因为它发现包含了您的字符。

String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());

一个更简单的方法(不用担心模式匹配)是将两个__abc都转换为小写:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
System.out.println("It's a match!");
}

你可以使用

org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");

Apache Commons库对于这类事情非常有用。这个特殊的表达式可能比正则表达式更好,因为正则表达式在性能方面总是代价高昂。

你可以使用正则表达式,它可以工作:

boolean found = s1.matches("(?i).*" + s2+ ".*");

我做了一个测试,找到一个字符串的大小写不敏感匹配。我有一个150000个对象的向量,所有对象都有一个字符串作为一个字段,并想找到匹配字符串的子集。我尝试了三种方法:

  1. 转换为小写

    for (SongInformation song: songs) {
    if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
    ...
    }
    }
    
  2. Use the String matches() method

    for (SongInformation song: songs) {
    if (song.artist.matches("(?i).*" + pattern + ".*")) {
    ...
    }
    }
    
  3. Use regular expressions

    Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher("");
    for (SongInformation song: songs) {
    m.reset(song.artist);
    if (m.find()) {
    ...
    }
    }
    

Timing results are:

  • No attempted match: 20 msecs

  • To lower match: 182 msecs

  • String matches: 278 msecs

  • Regular expression: 65 msecs

The regular expression looks to be the fastest for this use case.

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

如果您导入ICU4j,可以创建一些unicode友好的代码。我认为“忽略大小写”对于方法名称来说是有问题的,因为尽管主要强度比较确实忽略大小写,但它被描述为具体内容依赖于区域设置。但希望它以用户期望的方式依赖于语言环境。

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();
}

更快的实现:利用String.regionMatches()

使用regexp可能相对较慢。如果你只是想检查一种情况,(慢)没有关系。但如果你有一个数组或一个包含成千上万个字符串的集合,事情就会变得非常缓慢。

下面给出的解决方案既不使用正则表达式,也不使用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种方法。

  1. 我们的containsIgnoreCase ()方法。
  2. 通过将两个字符串转换为小写并调用String.contains()
  3. 通过将源字符串转换为小写,并使用预缓存的小写子字符串调用String.contains()。这个解决方案已经不那么灵活了,因为它测试的是一个predefend子字符串。
  4. 使用正则表达式(接受的答案Pattern.compile().matcher().find()…)
  5. 使用正则表达式,但使用预先创建和缓存的Pattern。这个解决方案已经不够灵活了,因为它测试预定义的子字符串。

结果(通过调用该方法1000万次):

  1. 我们的方法是670毫秒
  2. 2x toLowerCase() and contains(): 2829 ms
  3. 1x toLowerCase()和contains(),缓存子字符串:2446毫秒
  4. Regexp: 7180 ms
  5. 缓存Pattern的Regexp: 1845 ms

表格中的结果:

                                            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

我们的方法是快4倍,与小写和使用contains()相比,是快10倍,与使用正则表达式相比,也是快3倍,即使Pattern是预缓存的(并且失去了检查任意子字符串的灵活性)。


分析测试代码

如果你对分析是如何执行的感兴趣,下面是完整的可运行应用程序:

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");
}
    

}

如果你必须在另一个ASCII字符串中搜索一个ASCII字符串,比如URL,你会发现我的解决方案更好。我测试了icza和我的方法的速度,结果如下:

  • 案例1用了2788 ms - regionMatches
  • 病例2用了1520毫秒

代码:

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));
}

你可以简单地这样做:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
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");
}


}


}
"AbCd".toLowerCase().contains("abcD".toLowerCase())

我们可以使用流与anyMatch和Java 8的包含

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;
}


}

有一个简单简洁的方法,使用regex标志(不区分大小写{i}):

 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)
* */

或者你可以使用一个简单的方法,将字符串的大小写转换为子字符串的大小写,然后使用contains方法。