Каков самый быстрый метод поиска подстроки в Java
Мне нужно реализовать способ поиска подстроки (игл) в списке строки (haystack) с помощью Java.
В частности, мое приложение имеет список профилей пользователей. Если я наберу несколько букв, например, "Ja", а затем выполните поиск, тогда появятся все пользователи, чье имя содержит "ja". Например, результатом могут быть "Джек", "Джексон", "Джейсон", "Диажаф".
В Java, как я знаю, существует 3 встроенных метода для поиска подстроки в строке.
Мой вопрос: Каковы временные ряды каждого метода выше? который является самым быстрым или наиболее эффективным или наиболее популярным способом проверить, содержит ли строка строки заданную подстроку.
Я знаю, что существуют некоторые алгоритмы, которые делают то же самое, например алгоритм поиска строк Бойера-Мура, алгоритм Кнута-Морриса-Пратта и т.д. Я не хочу их использовать, потому что у меня есть небольшой список строк, и я думаю, что использование их для меня сейчас слишком велико. Кроме того, я должен ввести много дополнительного кода для такого алгоритма, который не является встроенным.
Если вы считаете, что мои мысли неверны, пожалуйста, не стесняйтесь меня исправлять.
Ответы
Ответ 1
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
Ответ 2
Принятый ответ неверен и не завершен.
-
indexOf()
выполняет наивный строковый поиск с использованием обратного отслеживания на несоответствиях. Это довольно быстро на маленьких рисунках/текстах, но показывает очень низкую производительность при больших текстах.
-
contains("ja")
должен быть сопоставим с indexOf (потому что он делегирует ему)
-
matches("ja")
не даст правильный результат, потому что он ищет точное соответствие (только строка "ja"
будет точно соответствовать)
-
Pattern p = Pattern.compile("ja"); Matcher m = p.matcher("jack"); m.find();
будет правильным способом поиска текстов с регулярными выражениями. На практике (с использованием больших текстов) это будет самый эффективный способ использования только java api. Это связано с тем, что постоянный шаблон (например, "ja"
) не обрабатывается двигателем регулярного выражения (который медленный), а алгоритмом Boyer-Moore (который выполняется быстро)
Ответ 3
Что касается трех, о которых вы спрашивали, регулярное выражение будет намного медленнее, потому что для этого требуется собрать полный конечный автомат, когда у вас гораздо более простая цель. Для contains
vs indexOf
...
2114 public boolean contains(CharSequence s) {
2115 return indexOf(s.toString()) > -1;
2116 }
(т.е. contains
просто вызывает indexOf
, но при каждом вызове может возникнуть дополнительное создание String
. Это всего лишь одна реализация contains
, но поскольку контракт contains
является упрощением indexOf
, это, вероятно, то, как каждая реализация будет работать.)
Ответ 4
Из примера в вашем вопросе, я предполагаю, что вы хотите делать нечувствительные к регистру сравнения. Это значительно замедляет процесс. Следовательно, если вы можете жить с некоторыми неточностями, которые могут зависеть от языка, в котором вам нужно выполнить сравнение, и ваш длинный текст будет искать снова и снова, может иметь смысл преобразовать длинный текст один раз в верхний регистр и строка поиска, а затем поиск нечувствителен к регистру.
Ответ 5
Если вы ищете большое количество строк, я прочитал алгоритм Aho-Corasick довольно быстро, но он реализован в Java. Это тот же алгоритм, который используется GREP в системах на базе Unix, если это помогает, и это довольно эффективно. Здесь - это реализация Java, предоставляемая Беркли.
Смотрите также: fooobar.com/questions/178069/...
Ответ 6
Это зависит от конкретной версии JRE (и даже JDK) make/version. Он также зависит/зависит от факторов как длина строки, вероятность того, что она будет содержаться, в каком положении и т.д. Единственный способ получения точных данных производительности требует настройки вашего точного контекста.
Однако, вообще говоря, aString.contains()
и aString.indexOf()
должны быть точно такими же. И даже если бы регулярное выражение было превосходно оптимизировано, оно не превышало бы производительность первых двух.
Нет, Java также не использует чрезвычайно специализированные алгоритмы.
Ответ 7
Тест в Kotlin (который использует Java в любом случае, поэтому результаты примерно одинаковы), на Android, используя аналогичный подход, как указано выше, показывает, что действительно 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()
}
}
}