Каков самый быстрый метод поиска подстроки в Java

Мне нужно реализовать способ поиска подстроки (игл) в списке строки (haystack) с помощью Java.

В частности, мое приложение имеет список профилей пользователей. Если я наберу несколько букв, например, "Ja", а затем выполните поиск, тогда появятся все пользователи, чье имя содержит "ja". Например, результатом могут быть "Джек", "Джексон", "Джейсон", "Диажаф".

В Java, как я знаю, существует 3 встроенных метода для поиска подстроки в строке.

  • string.contains()

  • String.indexOf()

  • регулярное выражение. это что-то вроде string.matches( "ja" ))

Мой вопрос: Каковы временные ряды каждого метода выше? который является самым быстрым или наиболее эффективным или наиболее популярным способом проверить, содержит ли строка строки заданную подстроку.

Я знаю, что существуют некоторые алгоритмы, которые делают то же самое, например алгоритм поиска строк Бойера-Мура, алгоритм Кнута-Морриса-Пратта и т.д. Я не хочу их использовать, потому что у меня есть небольшой список строк, и я думаю, что использование их для меня сейчас слишком велико. Кроме того, я должен ввести много дополнительного кода для такого алгоритма, который не является встроенным. Если вы считаете, что мои мысли неверны, пожалуйста, не стесняйтесь меня исправлять.

Ответ 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()
        }
    }
}