Stri"/>

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

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

How to check if a String contains another String in a case insensitive manner in Java?

说我有两个字符串,

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

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

1
return s1.contains(s2);

我很确定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());

    推荐阅读