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


386

说我有两个弦,

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

我要执行s2包含在中的支票退回s1。我可以这样做:

return s1.contains(s2);

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

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

除了所有这些,还有另一种(可能更好)的方法来完成此任务而不关心大小写敏感吗?


当文档使您失败时,DrJava将是一种极其简单的测试方法。只需在其“交互”窗口中输入几个测试用例,就应该找出答案。
EfForEffort

17
我认为您已经回答了自己的问题。我认为以下任何解决方案都不比这更好。但是它们肯定慢一些。
Nikolay Dimitrov 2014年

7
您的解决方案比答案中的任何一个都要简单
LobsterMan

2
我和许多人正在寻找的答案是您的问题。
拉利特·福兹达尔

1
您的示例是最简单,最易读且可能是最好的方法-比我看到的任何答案都要好。
user1258361

Answers:


320

是的,包含的内容区分大小写。您可以将java.util.regex.Pattern与CASE_INSENSITIVE标志一起使用,以区分大小写:

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

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


23
如的文档所述Pattern.CASE_INSENSITIVE,该功能仅适用于ASCII字符(即“Ä”与“ä”不匹配)。需要另外指定UNICODE_CASE标志以达到该目的。
菲利普·温德勒

72
这种方法使用的Pattern性能是否比s1.toLowerCase().contains(s2.toLowerCase())
拉贾特·古普塔

6
@ user01我执行了速度分析。有关结果的信息,请参阅我的答案(我也显示了更快的解决方案):stackoverflow.com/a/25379180/1705598
icza 2014年

10
如果我们有了更好的变量名,我会更清楚怎么回事:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
John Bowers

5
@ user01正确性先于性能,并且使用toLowerCase可能会带来不正确的结果(例如,当比较某些包含字母Sigma的希腊文本时,该字母具有相同的大写形式的两个小写形式)。
Klitos Kyriacou 2015年

266

Dave L.回答的一个问题是s2包含正则表达式标记(例如\d)等。

您要在s2上调用Pattern.quote():

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

1
不错,马特。我很想知道哪种方法更有效-小写字母包含的内容或您的模式解决方案。使用模式不是对单个比较而言效率较低,而对于多个比较而言效率较高吗?
亚伦

41
在大多数情况下,.toLowerCase()。contains()方法可能会更快。我可能更喜欢这种样式,以降低复杂性。
Matt Quail

3
@AaronFerguson是的,确实toLowerCase().contains()更快。我执行了一些速度分析,结果请参见我的答案:stackoverflow.com/a/25379180/1705598
icza 2014年

2
@MattQuail如果没有正确的地方,那就没有任何意义。例如,希腊大写字母sigma具有两种小写形式(取决于它是否在单词的末尾),并且当尝试进行不区分大小写的子字符串匹配时(子字符串以sigma结尾),您很容易会弄错结果。
Klitos Kyriacou 2015年

我认为我们也应该添加Pattern.UNICODE_CASE标志。您能确认一下吗?
Thariq Nugrohotomo '16

160

您可以使用

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

Apache的共享库是这样的事情是非常有用的。由于正则表达式在性能方面总是很昂贵,因此这一特定表达式可能比正则表达式更好。


1
有人知道这是否尊重语言环境吗?
查尔斯伍德

12
@CharlesWood它委托给String.regionMatches,它使用逐字符转换,所以没有。此外,containsIgnoreCase("ß", "ss")返回-1,这在每个语言环境中都是错误的(德国“ sharp s”大写为“ ss”。)
maaartinus 2014年

那么比较德语单词的正确方法是哪种呢?看来,这是一种使各种比较字符串的方法都变得复杂的语言:P
chomp

1
顺便说一句:德语在2017年正式以大写ß扩展:de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F。在德语键盘上,按Shift + Alt Gr +ß-> test:😁–
Kawu

119

更快的实现:利用 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种方法。

  1. 我们的 containsIgnoreCase()方法。
  2. 通过将两个字符串都转换为小写并调用String.contains()
  3. 通过将源字符串转换为小写并String.contains()使用预先缓存的小写子字符串进行调用。该解决方案已经不够灵活,因为它会测试predefiend子字符串。
  4. 使用正则表达式(可接受的答案Pattern.compile().matcher().find()...)
  5. 使用正则表达式,但具有预先创建和缓存的内容Pattern。该解决方案已经不够灵活,因为它会测试预定义的子字符串。

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

  1. 我们的方法:670毫秒
  2. 2x toLowerCase()和contains():2829毫秒
  3. 1个toLowerCase()和contains()带有缓存的子字符串:2446 ms
  4. 正则表达式:7180毫秒
  5. 正则表达式缓存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

我们的方法是快4倍相比lowercasing和使用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");
    }

}

6
+1,但请注意,它对于ß(德国尖锐S;大写为SS)以及其他一些字符(请参阅的源String.regionMatches,尝试两次转换)都失败。
maaartinus 2014年

2
您总是测试相同的字符串,这实际上不是一个公平的比较。“我是”始终位于中间,这可能会或可能不会对不同的搜索方法有所不同。最好是生成随机字符串,并在不存在子字符串时报告速度。

2
这似乎是非常接近的Apache StringUtils的方法:grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/...
alain.janinm

1
@ alain.janinm我看不到相似之处。唯一与之接近的StringUtils.containsIgnoreCase()是,我的解决方案和Apache都使用一个regionMatches()方法(一个周期),但是即使这与我调用的String.regionMatches()和Apache调用的方法也不相同CharSequenceUtils.regionMatches()
icza 2014年

2
@icza 实际上CharSequenceUtils.regionMatches只是在打电话String.regionMatches。无论如何,我的意思是提供信息,如果有人已经在使用StringUtils lib,他就可以调用它,因为这似乎是一种有效的方法,就像您通过基准测试来证明它一样。如果我没有使用Apache lib,那我一定会使用你的方法;)
alain.janinm 2014年

22

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

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

4
字符大小写取决于语言,这意味着它可以在您的计算机上运行,​​但对客户而言会失败:)。参见@Adriaan Koster评论。
kroiz 2012年

1
@kroiz,这取决于String的来源。比较“ foobar”和“ FOO”将始终匹配,但是,如果您要比较用户输入的信息或特定于语言的内容,那么您是对的-开发人员应谨慎。
Phil

16

是的,这是可以实现的:

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


12
使用toLowerCase()的一大缺点是结果取决于当前的语言环境。请参阅:javapapers.com/core-java/...
阿德里安·科斯特

4
该问题实际上包含一个更好的解决方案,因为该问题对于non-lowercase不适用s2。不谈论诸如此类细节的问题,如果不编译,它将返回一个字符串。
maaartinus 2014年


3

如果拉入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();
}

3

我进行了测试,发现字符串的大小写不敏感。我有一个150,000个对象的Vector,都带有一个字符串作为一个字段,并想找到与字符串匹配的子集。我尝试了三种方法:

  1. 全部转换成小写

    for (SongInformation song: songs) {
        if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
                ...
        }
    }
  2. 使用String Matches()方法

    for (SongInformation song: songs) {
        if (song.artist.matches("(?i).*" + pattern + ".*")) {
        ...
        }
    }
  3. 使用正则表达式

    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

有一种简单的简洁方法,使用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)
 * */

0

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


0
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循环,您会发现它会在容器字符串上的子字符串(即“ sub”的长度)中进行迭代。每次迭代都会检查容器字符串的子字符串是否等于子字符串。
赛斯,2014年

@您可能应该将其添加到您的答案中。
帽子的家伙

2
这是有史以来最慢的方法...对于德国人来说也是失败的
maaartinus 2014年

0

如果必须在另一个ASCII字符串(例如URL)中搜索ASCII字符串,您会发现我的解决方案更好。我已经测试了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));
}

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

    }

}

感谢您提供此代码段,它可能会提供一些有限的短期帮助。通过说明为什么这是一个解决问题的好方法,适当的解释将大大提高其长期价值,对于其他存在类似问题的读者来说,这样做将更为有用。请编辑您的答案以添加一些解释,包括您所做的假设。
Toby Speight '18


0

我们可以将流与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;
    }

}

0

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


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

-1

您可以简单地执行以下操作:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.