什么是Java中最快的子字符串搜索方法

时间:2013-08-20 16:16:47

标签: java regex search substring search-engine

我需要使用Java实现一种在字符串(haystack)列表中搜索子字符串(针)的方法。

更具体地说,我的应用程序有一个用户配置文件列表。如果我输入一些字母,例如“Ja”,然后搜索,则所有名称中包含“ja”的用户都应该显示。例如,结果可能是“Jack”,“Jackson”,“Jason”,“Dijafu”。

在Java中,据我所知,有3种内置方法可以在字符串中查看搜索子字符串。

  1. string.contains()

  2. string.indexOf()

  3. 正则表达式。它类似于string.matches(“ja”))

  4. 我的问题是:上述每种方法的运行时间是多少?哪一个是检查字符串列表是否包含给定子字符串的最快或最有效或最流行的方法。

    我知道存在一些做同样事情的算法,例如Boyer-Moore字符串搜索算法,Knuth-Morris-Pratt算法等。我不想使用它们,因为我只有一小串字符串,我认为使用它们对我来说有点矫枉过正。此外,我必须为这种非内置算法输入许多额外的编码。 如果您认为我的想法不正确,请随时纠正我。

7 个答案:

答案 0 :(得分:21)

接受的答案不正确且不完整。

  • indexOf()使用不匹配的回溯进行天真的字符串搜索。这对于小图案/文本来说非常快,但在大文本上表现非常糟糕
  • contains("ja")应该与indexOf相当(因为它委托给它)
  • matches("ja")无法提供正确的结果,因为它会搜索完全匹配(只有字符串"ja"才会完全匹配)
  • Pattern p = Pattern.compile("ja"); Matcher m = p.matcher("jack"); m.find();是查找带正则表达式的文本的正确方法。在实践中(使用大文本)它将是仅使用java api的最有效的方式。这是因为正则表达式(如"ja")不会被正则表达式引擎处理(这很慢),而是由Boyer-Moore算法处理(这很快)

答案 1 :(得分:5)

就你所问的三个而言,正则表达式会慢得多,因为当你有一个更简单的目标时,它需要组合一个完整的状态机。对于contains vs indexOf ...

2114 public boolean contains(CharSequence s) {
2115     return indexOf(s.toString()) > -1;
2116 }

(即contains只调用indexOf,但您可能会在每次调用时产生额外的String次创建。这只是contains的一个实现,但自从contains的合同是indexOf的简化,这可能是每个实现都有效的方式。)

答案 2 :(得分:5)

String[] names = new String[]{"jack", "jackson", "jason", "dijafu"};
long start = 0;
long stop = 0;

//Contains
start = System.nanoTime();
for (int i = 0; i < names.length; i++){
    names[i].contains("ja");
}
stop = System.nanoTime();
System.out.println("Contains: " + (stop-start));

//IndexOf
start = System.nanoTime();
for (int i = 0; i < names.length; i++){
    names[i].indexOf("ja");
}
stop = System.nanoTime();
System.out.println("IndexOf: " + (stop-start));

//Matches
start = System.nanoTime();
for (int i = 0; i < names.length; i++){
    names[i].matches("ja");
}
stop = System.nanoTime();
System.out.println("Matches: " + (stop-start));

输出:

Contains: 16677
IndexOf: 4491
Matches: 864018

答案 3 :(得分:1)

从您问题中的示例中,我假设您要进行不区分大小写的比较。这些都大大减缓了这个过程。因此,如果你可以忍受一些不准确 - 这可能取决于你需要进行比较的区域设置,并且你的长文本被反复搜索,将长文本一次转换为大写可能是有意义的,搜索字符串,然后搜索不区分大小写。

答案 4 :(得分:1)

如果您正在搜索大量字符串,我已经阅读Aho-Corasick算法非常快,但它本身是用Java实现的。这与GREP在基于Unix的系统中使用的算法相同,如果这有帮助并且非常有效。 Here是由Berkley提供的Java实现。

另请参阅:https://stackoverflow.com/a/1765616/59087

答案 5 :(得分:0)

这取决于特定的JRE(甚至是JDK)make / version。它还取决于/可能取决于字符串长度,被包含的概率,在什么位置等因素。获得精确性能数据的唯一方法需要设置您的确切上下文。

但是,通常aString.contains()aString.indexOf()应完全相同。即使正则表达式得到了极好的优化,它也不会超过前两个表达式。

不,Java也没有使用非常专业的算法。

答案 6 :(得分:0)

使用与上述类似的方法,在Android上使用Kotlin的基准测试(无论如何仍使用Java,因此结果大致相同)表明确实containsindexOf类似,但对于即使使用它,某些原因也会更快。

至于正则表达式,因为它创建了真实的对象,并且可以走得更远,所以速度较慢。

采样结果(以毫秒为单位):

Contains: 0
IndexOf: 5
Matches: 45

代码:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        AsyncTask.execute {
            val itemsCount = 1000
            val minStringLength = 1000
            val maxStringLength = 1000
            val list = ArrayList<String>(itemsCount)
            val r = Random()
            val stringToSearchFor = prepareFakeString(r, 5, 10, ALPHABET_LOWERCASE + ALPHABET_UPPERCASE + DIGITS)
            for (i in 0 until itemsCount)
                list.add(prepareFakeString(r, minStringLength, maxStringLength, ALPHABET_LOWERCASE + ALPHABET_UPPERCASE + DIGITS))
            val resultsContains = ArrayList<Boolean>(itemsCount)
            val resultsIndexOf = ArrayList<Boolean>(itemsCount)
            val resultsRegEx = ArrayList<Boolean>(itemsCount)
            //Contains
            var start: Long = System.currentTimeMillis()
            var stop: Long = System.currentTimeMillis()
            for (str in list) {
                resultsContains.add(str.contains(stringToSearchFor))
            }
            Log.d("AppLog", "Contains: " + (stop - start))
            //IndexOf
            start = System.currentTimeMillis()
            for (str in list) {
                resultsIndexOf.add(str.indexOf(stringToSearchFor) >= 0)
            }
            stop = System.currentTimeMillis()
            Log.d("AppLog", "IndexOf: " + (stop - start))
            //Matches
            val regex = stringToSearchFor.toRegex()
            start = System.currentTimeMillis()
            for (str in list) {
                resultsRegEx.add(regex.find(str) != null)
            }
            stop = System.currentTimeMillis()
            Log.d("AppLog", "Matches: " + (stop - start))
            Log.d("AppLog", "checking results...")
            var foundIssue = false
            for (i in 0 until itemsCount) {
                if (resultsContains[i] != resultsIndexOf[i] || resultsContains[i] != resultsRegEx[i]) {
                    foundIssue = true
                    break
                }
            }
            Log.d("AppLog", "done. All results are the same?${!foundIssue}")
        }
    }

    companion object {
        const val ALPHABET_LOWERCASE = "qwertyuiopasdfghjklzxcvbnm"
        const val ALPHABET_UPPERCASE = "QWERTYUIOPASDFGHJKLZXCVBNM"
        const val DIGITS = "1234567890"

        fun prepareFakeString(r: Random, minLength: Int, maxLength: Int, charactersToChooseFrom: String): String {
            val length = if (maxLength == minLength) maxLength else r.nextInt(maxLength - minLength) + minLength
            val sb = StringBuilder(length)
            for (i in 0 until length)
                sb.append(charactersToChooseFrom[r.nextInt(charactersToChooseFrom.length)])
            return sb.toString()
        }
    }
}