Javaを使用して文字列(haystack)のリスト内の部分文字列(針)を検索する方法を実装する必要があります。
具体的には、私のアプリにはユーザープロファイルのリストがあります。 「Ja」などの文字を入力して検索すると、名前に「ja」が含まれているすべてのユーザーが表示されます。たとえば、結果は「Jack」、「Jackson」、「Jason」、「Dijafu」になります。
Javaには、私が知っているように、文字列内の検索部分文字列を表示するための3つの組み込みメソッドがあります。
string.contains()
string.indexOf()
正規表現。 string.matches( "ja"))のようなものです
私の質問は:上記の各メソッドのランタイムは何ですか?文字列のリストに特定の部分文字列が含まれているかどうかを確認するための最も速い、最も効率的な、または最も一般的な方法はどれですか。
ボイヤー-ムーア文字列検索アルゴリズム、クヌース-モリス-プラットアルゴリズムなど、同じことを行うアルゴリズムがいくつか存在することを私は知っています。文字列のリストが少ないので使いたくありません。今のところ、それらを使うのはちょっとやり過ぎだと思います。また、このような非組み込みアルゴリズムには、多くの追加コーディングを入力する必要があります。私の考えが正しくないと思われる場合は、遠慮なく訂正してください。
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
受け入れられた答えは正しくなく、完全ではありません。
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(速い)によってあなたが尋ねた3つに関しては、正規表現は、はるかに単純なターゲットがある場合に完全なステートマシンを組み立てる必要があるため、はるかに遅くなります。 contains
とindexOf
..の場合.
2114 public boolean contains(CharSequence s) {
2115 return indexOf(s.toString()) > -1;
2116 }
(つまり、contains
はindexOf
を呼び出すだけですが、呼び出しごとに追加のString
が作成される可能性があります。これはcontains
の実装の1つにすぎませんが、 contains
のコントラクトはindexOf
を単純化したものであり、これがおそらくすべての実装が機能する方法です。)
あなたの質問の例から、大文字と小文字を区別しない比較をしたいと思います。それらはプロセスをかなり遅くします。したがって、比較を行う必要のあるロケールに依存する可能性のあるいくつかの不正確さを抱えて生きることができ、長いテキストが何度も検索される場合は、長いテキストを一度大文字に変換することが理にかなっている可能性があります。検索文字列も検索し、大文字と小文字を区別せずに検索します。
大量の文字列を検索している場合、私が読んだ Aho-Corasick アルゴリズムはかなり高速ですが、Javaでネイティブに実装されています。これは、UnixベースのシステムでGREPが使用するのと同じアルゴリズムであり、それが役立つ場合は非常に効率的です。 ここ はJava実装はBerkleyの好意によるものです。
これは、特定のJRE(およびJDK)の製造元/バージョンによって異なります。また、文字列の長さ、含まれる確率、どの位置などの要因にも依存します。正確なパフォーマンスデータを取得する唯一の方法は、正確なコンテキストを設定することです。
ただし、一般に、aString.contains()
とaString.indexOf()
は完全に同じである必要があります。また、正規表現が見事に最適化されていても、最初の2つのパフォーマンスを超えることはありません。
いいえ、Javaは非常に特殊なアルゴリズムも使用していません。
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()
}
}
}