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】:就您询问的三个而言,正则表达式会慢得多,因为当您有一个更简单的目标时,它需要组合一个完整的状态机。对于contains
与indexOf
...
2114 public boolean contains(CharSequence s)
2115 return indexOf(s.toString()) > -1;
2116
(即contains
只是调用indexOf
,但您可能会在每次调用时产生额外的String
创建。这只是contains
的一种实现,但由于contains
的合同是一种简化indexOf
,这可能是每个实现的工作方式。)
【讨论】:
没有额外的字符串创建——s
是String
,toString()
将返回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中最快的子字符串搜索方法是啥的主要内容,如果未能解决你的问题,请参考以下文章