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

Posted

技术标签:

【中文标题】如何在Java中以不区分大小写的方式检查一个字符串是不是包含另一个字符串?【英文标题】:How to check if a String contains another String in a case insensitive manner in Java?如何在Java中以不区分大小写的方式检查一个字符串是否包含另一个字符串? 【发布时间】:2010-09-10 08:23:06 【问题描述】:

假设我有两个字符串,

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

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

return s1.contains(s2);

我很确定contains() 区分大小写,但是我无法通过阅读文档确定这一点。如果是这样,我想我最好的方法是:

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

除此之外,是否有另一种(可能更好)的方式来实现这一点而不关心区分大小写?

【问题讨论】:

DrJava 在文档失败时是一种非常简单的测试方法。只需在其交互窗口中输入几个测试用例,您就会发现。 我想你已经回答了你自己的问题。我不认为下面的任何解决方案都比这更好。但它们肯定更慢。 您的解决方案比答案中的任何一个都简单 你的例子是最简单、最易读的,而且可能是最好的方法——比我看到的任何答案都好。 【参考方案1】:

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

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

编辑:如果 s2 包含正则表达式特殊字符(其中有很多),首先引用它很重要。我已经更正了我的答案,因为这是人们会看到的第一个答案,但自从 Matt Quail 指出这一点后,我就投票赞成。

【讨论】:

正如Pattern.CASE_INSENSITIVE 的文档所述,这仅适用于 ASCII 字符(即“Ä”与“ä”不匹配)。需要另外指定UNICODE_CASE 标志来实现这一点。 这种方法使用Pattern 的性能是否比s1.toLowerCase().contains(s2.toLowerCase()) 更高? @user01 我进行了速度分析。查看我的答案以获得结果(我还展示了一个更快的解决方案):***.com/a/25379180/1705598 如果我们有更好的变量名,我会更清楚发生了什么:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find() @user01 正确性先于性能,使用 toLowerCase 可能会产生不正确的结果(例如,在比较某些包含字母 Sigma 的希腊文本时,相同的大写形式有两个小写形式)。 【参考方案2】:

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

【讨论】:

他在问如何匹配区分大小写的字符串。【参考方案3】:

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

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

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

【讨论】:

不错,马特。我很想知道哪种方法更有效-小写包含或您的模式解决方案。使用模式不是对单次比较效率较低,而是对多次比较更有效? .toLowerCase().contains() 方法在大多数情况下可能会更快。我也可能更喜欢这种风格,因为它的复杂性较低。 @AaronFerguson 是的,确实,toLowerCase().contains() 更快。我进行了一些速度分析,结果见我的答案:***.com/a/25379180/1705598 @MattQuail 如果它可能不正确,那么更快是没有意义的。例如,希腊大写的 sigma 有两种小写形式(取决于它是否出现在单词的末尾),当尝试进行不区分大小写的子字符串匹配时,子字符串以 sigma 结尾,你很容易得到错误结果。 我认为我们也应该添加Pattern.UNICODE_CASE 标志。你能确认一下吗?【参考方案4】:

是的,这是可以实现的:

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!”因为它发现你的角色被收容了。

【讨论】:

使用 toLowerCase() 的一大缺点是结果取决于当前的语言环境。见:javapapers.com/core-java/… 这个问题实际上包含一个更好的解决方案,因为这个问题对于非小写s2 失败。不谈论这样的细节,这个不能编译,如果编译,它会返回一个字符串。【参考方案5】:
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());

【讨论】:

【参考方案6】:

一种更简单的方法(无需担心模式匹配)是将Strings 都转换为小写:

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

【讨论】:

字符大小写取决于语言,这意味着它可以在您的计算机上工作,但对客户来说会失败:)。见@Adriaan Koster 评论。 @kroiz,这取决于字符串的来源。比较“foobar”和“FOO”将始终匹配,但是如果您要比较用户输入信息或特定语言的内容,那么您是对的 - 开发人员应该谨慎。 与其他语言相比,大写实际上是一个更好的转换(例如微软推荐的)【参考方案7】:

你可以使用

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

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

【讨论】:

有人知道这是否尊重语言环境吗? @CharlesWood 它委托给String.regionMatches,它使用字符转换,所以没有。此外,containsIgnoreCase("ß", "ss") 返回 -1,这在所有语言环境中都是错误的(德语“sharp s”大写为“ss”。 那么比较德语单词的正确方法是什么?似乎这是一种使比较字符串的各种方式变得复杂的语言:P 顺便说一句:德语在 2017 年正式扩展为大写 ß:de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F。在德语键盘上,键入 Shift + Alt Gr + ß -> 测试:ẞ ? 类似地,您可能在土耳其语(例如伊斯坦布尔)与许多非土耳其语中实际使用的带点和不带点的 i İ 和 ı I 大写/小写时遇到问题大写/小写字母 I i. 的元音略有不同【参考方案8】:

你可以使用regular expressions,它可以工作:

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

【讨论】:

【参考方案9】:

我做了一个测试,找到一个不区分大小写的字符串匹配。我有一个包含 150,000 个对象的向量,所有对象都以字符串作为一个字段,并希望找到与字符串匹配的子集。我尝试了三种方法:

    全部转为小写

    for (SongInformation song: songs) 
        if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) 
                ...
        
    
    

    使用字符串matches()方法

    for (SongInformation song: songs) 
        if (song.artist.matches("(?i).*" + pattern + ".*")) 
        ...
        
    
    

    使用正则表达式

    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 毫秒

对于这个用例来说,正则表达式看起来是最快的。

【讨论】:

很好,你把计时结果。每个人都说正则表达式有多慢,但实际上只要编译一次正则表达式就非常快。【参考方案10】:
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

【讨论】:

基本上它是一个接受两个字符串的方法。它应该是 contains() 的不区分大小写的版本。使用 contains 方法时,您想查看一个字符串是否包含在另一个字符串中。此方法采用字符串 "sub" 并检查它是否等于容器字符串的子字符串,它们的长度等于 "sub" 。如果您查看 for 循环,您会看到它在容器字符串上迭代子字符串(即“子”的长度)。每次迭代都会检查容器字符串的子字符串是否与子字符串相等。 @您可能应该将其添加到您的答案中。 这是有史以来最慢的方法......而且德语也失败了。【参考方案11】:

如果您使用 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();

【讨论】:

【参考方案12】:

更快的实现:利用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。这个解决方案已经不那么灵活了,因为它测试了一个预定义的子字符串。

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

    我们的方法:670 毫秒 2x toLowerCase() 和 contains():2829 毫秒 1x toLowerCase() 和 contains() 缓存子字符串: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

与小写和使用 contains() 相比,我们的方法快 4 倍,与使用正则表达式相比,我们的方法快 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");
    


【讨论】:

+1 但请注意,对于ß(德语锐利S;大写为SS)和其他一些字符(请参阅String.regionMatches 的源代码,它会尝试两种转换) . 你总是测试相同的字符串,这并不是一个公平的比较。 “我是”总是在中间,这可能会或可能不会对不同的搜索方法产生影响。更好的是生成随机字符串,并在子字符串不存在时报告速度。 这似乎非常接近 Apache StringUtils 方法:grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/… @alain.janinm 我看不到相似之处。与StringUtils.containsIgnoreCase() 似乎“接近”的唯一一件事是我的解决方案和 Apache 都使用regionMatches() 方法(在一个循环中),但即使这与我调用 String.regionMatches() 和 Apache 调用 @ 也不相同987654343@. @icza CharSequenceUtils.regionMatches 实际上只是调用String.regionMatches。无论如何,我的意思是提供信息,如果有人已经在使用 StringUtils lib,他可以调用它,因为这似乎是一种有效的方式,就像你用你的基准测试证明它一样。如果我没有使用 Apache lib,我肯定会使用你的方法 ;)【参考方案13】:

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

案例 1 耗时 2788 毫秒 - 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));

【讨论】:

【参考方案14】:

你可以简单地做这样的事情:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);

【讨论】:

【参考方案15】:
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("[\\pInCombiningDiacriticalMarks]", "").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("[\\pInCombiningDiacriticalMarks]", ""), key)) 
            System.out.println("OK");
         else 
            System.out.println("KO");
        

    


【讨论】:

感谢您提供此代码 sn-p,它可能会提供一些有限的短期帮助。一个正确的解释would greatly improve 它的长期价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。【参考方案16】:
"AbCd".toLowerCase().contains("abcD".toLowerCase())

【讨论】:

您能否通过解释您的代码如何解决问题来改进您的答案? 这个答案已经在其他人提供的这个问题的许多其他更详细的答案中提出。我认为这个答案在这里没有任何用处。【参考方案17】:

我们可以使用带有 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;
    


【讨论】:

【参考方案18】:

有一个简单简洁的方法,使用正则表达式标志(不区分大小写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)
 * */

【讨论】:

【参考方案19】:

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

【讨论】:

【参考方案20】:

一种方法是使用 toLowerCase() 或 toUpperCase() 方法将两个字符串转换为小写或大写并进行测试。

public class Sample 
   public static void main(String args[])
      String str = "Hello Welcome to insensitive Container";
      String test = "Java Testing";
      Boolean bool = str.toLowerCase().contains(test.toLowerCase());
      System.out.println(bool);
   

这是使用带有 CASE_INSENSITIVE 标志的 java.util.regex.Pattern 进行不区分大小写匹配的另一种方法。

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

【讨论】:

以上是关于如何在Java中以不区分大小写的方式检查一个字符串是不是包含另一个字符串?的主要内容,如果未能解决你的问题,请参考以下文章

Java中以不寻常方式定义的对象回调

可以使 PHP 的 glob() 以不区分大小写的方式查找文件吗?

如何grep文件中不区分大小写的字符串?

如何在 Flask/Jinja 模板中以不同于过滤器的方式使用管道?

Go中不区分大小写的字符串比较

是否有 C# 不区分大小写的等于运算符?