【问题标题】: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 种内置方法可以查看字符串中的搜索子字符串。

  1. string.contains()

  2. string.indexOf()

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

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

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

【问题讨论】:

标签: java regex search substring search-engine


【解决方案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)。第三:基准是手写的(见stackoverflow.com/questions/504103/…)。我将编写一个单独的解决方案来纠正这些事实。
    • 这个基准没有任何价值。一个错误构建的微基准测试将使contains() 的性能比实际性能更差,因为它是第一个被测试的并且没有为 JVM 预热。
    • 这个答案是我最近读到的最有用的东西之一
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      另见:https://stackoverflow.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()
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2014-08-27
            • 1970-01-01
            • 1970-01-01
            • 2011-03-12
            • 1970-01-01
            • 2021-09-07
            • 1970-01-01
            • 2011-03-20
            • 1970-01-01
            相关资源
            最近更新 更多