【Java必修课】判断String是否包含子串的四种方法及性能对比

2022-10-09,,,,

1 简介

判断一个字符串是否包含某个特定子串是常见的场景,比如判断一篇文章是否包含敏感词汇、判断日志是否有error信息等。本文将介绍四种方法并进行性能测试。

2 四种方法

2.1 jdk原生方法string.indexof

string的函数中,提供了indexof(substr)方法,返回子串substr第一次出现的位置,如果不存在则返回-1。例子如下:

//包含java
assertequals(7, "pkslow java".indexof("java"));
//如果包含多个,返回第一次出现位置
assertequals(0, "java java".indexof("java"));
//大小写敏感
assertequals(-1, "google guava".indexof("guava"));

2.2 jdk原生方法string.contains

最直观判断的方法是contains(substr),返回类型为boolean,如果包含返回true,不包含则返回false。例子如下:

//包含java
asserttrue("code in java".contains("java"));
//大小写敏感,不包含go
assertfalse("let's go".contains("go"));
//转为大写后包含
asserttrue("let's go".touppercase().contains("go"));

实际上,stringcontains方法是通过调用indexof方法来判断的,源码如下:

public boolean contains(charsequence s) {
  return indexof(s.tostring()) > -1;
}

2.3 jdk原生正则匹配pattern

通过强大的正则匹配来判断,虽然有点杀鸡用牛刀的感觉,但也不是不能用,例子如下:

pattern pattern = pattern.compile("java");
//包含java
matcher matcher1 = pattern.matcher("python, java, go, c++");
asserttrue(matcher1.find());
//不包含java
matcher matcher2 = pattern.matcher("python, c, go, matlab");
assertfalse(matcher2.find());

2.4 apache库stringutils.contains

apache的commons-lang3提供许多开箱即用的功能,stringutils就提供了许多与字符串相关的功能,例子如下:

//包含sub
asserttrue(stringutils.contains("string substring", "sub"));
//大小写敏感
assertfalse(stringutils.contains("this is java", "java"));
//忽略大小写
asserttrue(stringutils.containsignorecase("this is java", "java"));

3 性能对比

我们使用jmh工具来对四种方法进行性能测试,maven引入代码如下:

<dependency>
  <groupid>org.openjdk.jmh</groupid>
  <artifactid>jmh-core</artifactid>
  <version>${openjdk.jmh.version}</version>
</dependency>
<dependency>
  <groupid>org.openjdk.jmh</groupid>
  <artifactid>jmh-generator-annprocess</artifactid>
  <version>${openjdk.jmh.version}</version>
</dependency>

测试代码如下:

@benchmarkmode(mode.averagetime)
@outputtimeunit(timeunit.nanoseconds)
public class stringcontainsperformancetest {
    @state(scope.thread)
    public static class mystate {
        private string text = "if you want to be smart; read. if you want to be really smart; read a lot.";
        pattern pattern = pattern.compile("read");
    }

    @benchmark
    public int indexof(mystate state) {
        return state.text.indexof("read");
    }

    @benchmark
    public boolean contains(mystate state) {
       return state.text.contains("read");
    }

    @benchmark
    public boolean stringutils(mystate state) {
        return stringutils.contains(state.text, "read");
    }

    @benchmark
    public boolean pattern(mystate state) {
        return state.pattern.matcher(state.text).find();
    }

    public static void main(string[] args) throws exception {
        options options = new optionsbuilder()
                .include(stringcontainsperformancetest.class.getsimplename())
                .threads(6)
                .forks(1)
                .warmupiterations(3)
                .measurementiterations(6)
                .shouldfailonerror(true)
                .shoulddogc(true)
                .build();
        new runner(options).run();
    }
}

测试结果如下:

benchmark    mode  cnt    score    error  units
contains     avgt    6   11.331 ±  1.435  ns/op
indexof      avgt    6   11.250 ±  1.822  ns/op
pattern      avgt    6  101.196 ± 12.047  ns/op
stringutils  avgt    6   29.046 ±  3.873  ns/op

最快的就是indexof方法,其次是contains方法,二者应该没有实际区别,contains是调用indexof来实现的。apache的stringutils为第三方库,相对慢一些。最慢的是使用了正则的pattern的方法,这不难理解,正则引擎的匹配是比较耗性能的。

4 总结

本文介绍了判断一个字符串是否包含某个特定子串的四种方法,并通过性能测试进行了对比。其中性能最好的是string的indexof方法和contains方法,建议使用contains方法,性能好,跟indexof相比,更直观,更不容易犯错。毕竟让每个人时刻记住返回-1代表不存在也不是一件容易的事。

但是,使用indexofcontains方法都需要注意做判空处理,这时stringutils的优势就体现出来了。


欢迎关注公众号<南瓜慢说>,将持续为你更新...

多读书,多分享;多写作,多整理。

《【Java必修课】判断String是否包含子串的四种方法及性能对比.doc》

下载本文的Word格式文档,以方便收藏与打印。