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

Posted

技术标签:

【中文标题】Java中最快的子字符串搜索方法是啥【英文标题】:what is the fastest substring search method in JavaJava中最快的子字符串搜索方法是什么 【发布时间】:2013-08-22 19:15:03 【问题描述】:

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

更具体地说,我的应用有一个用户个人资料列表。如果我输入一些字母,例如“Ja”,然后搜索,那么名称中包含“ja”的所有用户都应该显示出来。例如,结果可能是“Jack”、“Jackson”、“Jason”、“Dijafu”。

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

    string.contains()

    string.indexOf()

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

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

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

【问题讨论】:

为什么认为子串搜索是性能问题? 这里不错***.com/questions/5296268/… 自己设置一些简单的性能测试应该不会太复杂! 您可能还想研究一下特里:en.wikipedia.org/wiki/Trie 【参考方案1】:

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

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

(即contains 只是调用indexOf,但您可能会在每次调用时产生额外的String 创建。这只是contains 的一种实现,但由于contains 的合同是一种简化indexOf,这可能是每个实现的工作方式。)

【讨论】:

没有额外的字符串创建——sStringtoString() 将返回this(所以没有对象创建)——或者s 不是@987654335 @ 在这种情况下,您需要在调用 indexOf 之前将其转换为 String。所以如果你需要的是contains,那就用contains吧。【参考方案2】:

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

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

不,Java 也不使用极其专业的算法。

【讨论】:

【参考方案3】:
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

【讨论】:

公平地说,您应该编译一次Pattern 并重复使用它。在同一正则表达式的循环中调用String.matches(String) 效率低下。 Pattern p = Pattern.compile("ja"); for(String s : names) p.matcher(s).matches(); 因为它只有 4 个,所以确实有很大的不同。运行之间的差异大于切换到在 for 循环之外创建模式的差异。 这个解决方案 - 即使被接受 - 也不正确。首先:matches() 的使用方式错误。其次,测试样本有偏差(更喜欢 indexOf)。第三:基准是手写的(见***.com/questions/504103/…)。我将编写一个单独的解决方案来纠正这些事实。 这个基准没有任何价值。一个错误构建的微基准测试将使contains() 的性能比实际性能更差,因为它是第一个被测试的并且没有为 JVM 预热。 这个答案是我最近读到的最有用的东西之一【参考方案4】:

根据您问题中的示例,我假设您想要进行不区分大小写的比较。那些大大减慢了这个过程。因此,如果您可以忍受一些不准确 - 这可能取决于您需要进行比较的语言环境,并且一次又一次搜索您的长文本,将长文本一次转换为大写可能是有意义的,并且搜索字符串,然后搜索不区分大小写。

【讨论】:

【参考方案5】:

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

另见:https://***.com/a/1765616/59087

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

android 上使用与上述类似的方法的 Kotlin 基准测试(无论如何都使用 Java,因此结果大致相同)表明 contains 确实类似于 indexOf,但由于某种原因更快,即使它使用它。

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

示例结果(以毫秒为单位的时间):

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()
        
    

【讨论】:

以上是关于Java中最快的子字符串搜索方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

在字符串集合中搜索的最快方法

以最快的方式找到所有可能的子字符串[重复]

在给定字符串中搜索字符集的最快算法

nowcoder-练习赛16

如何在 iOS 自动化脚本中搜索字符串中的子字符串? indexOf() 和 search() 方法不起作用

在Java中创建长度为'x'的字符串的最快方法全部由char'c'组成[重复]