スタックオーバーフローに関する以下の質問の後に
https://stackoverflow.com/questions/5564610/fast-alernative-for-stringindexofstring-str
Java(少なくとも6つ)より効率的な実装を使用しないのはなぜですか?)
コードは次のとおりです。
Java.lang.String#indexOf(String str)
1762 static int indexOf(char[] source, int sourceOffset, int sourceCount,
1763 char[] target, int targetOffset, int targetCount,
1764 int fromIndex) {
1765 if (fromIndex >= sourceCount) {
1766 return (targetCount == 0 ? sourceCount : -1);
1767 }
1768 if (fromIndex < 0) {
1769 fromIndex = 0;
1770 }
1771 if (targetCount == 0) {
1772 return fromIndex;
1773 }
1774
1775 char first = target[targetOffset];
1776 int max = sourceOffset + (sourceCount - targetCount);
1777
1778 for (int i = sourceOffset + fromIndex; i <= max; i++) {
1779 /* Look for first character. */
1780 if (source[i] != first) {
1781 while (++i <= max && source[i] != first);
1782 }
1783
1784 /* Found first character, now look at the rest of v2 */
1785 if (i <= max) {
1786 int j = i + 1;
1787 int end = j + targetCount - 1;
1788 for (int k = targetOffset + 1; j < end && source[j] ==
1789 target[k]; j++, k++);
1790
1791 if (j == end) {
1792 /* Found whole string. */
1793 return i - sourceOffset;
1794 }
1795 }
1796 }
1797 return -1;
1798 }
「効率」はすべてトレードオフに関するものであり、「最良の」アルゴリズムは多くの要因に依存します。 indexOf()
の場合、これらの要因の1つは、予想される文字列のサイズです。
JDKのアルゴリズムは、既存の文字配列への単純なインデックス付き参照に基づいています。参照するKnuth-Morris-Prattは、入力文字列と同じサイズの新しいint[]
を作成する必要があります。 Boyer-Moore の場合、いくつかの外部テーブルが必要ですが、そのうちの少なくとも1つは2次元です(私が思うに、B-Mを実装したことがありません)。
したがって、問題は次のようになります。追加のオブジェクトの割り当てとルックアップテーブルの作成は、アルゴリズムのパフォーマンスの向上によって相殺されていますか?覚えておいてください、私たちはO(N2)をO(N)に変更しますが、Nごとに実行するステップ数を減らすだけです。
そして、JDKデザイナーが「X文字未満の文字列の場合、単純なアプローチの方が速く、それより長い文字列の通常の使用は期待できません。長い文字列を使用する人は、最適化の方法を知っているでしょう。彼らの検索。」
誰もが知っている標準的な効率的な文字列検索アルゴリズムは Boyer-Moore です。特に、文字セットと同じサイズの transition table を作成する必要があります。 ASCIIの場合、これは256エントリの配列です。これは、長い文字列に見合う一定のオーバーヘッドであり、小さな文字列をだれでも気にするほど遅くすることはありません。しかし、Javaは、テーブルのサイズを64Kにする2バイト文字を使用します。通常の使用では、このオーバーヘッドはBoyer-Mooreからの予想されるスピードアップを超えるため、Boyer-Mooreは価値がありません。
もちろん、そのテーブルのほとんどは同じエントリを持つので、例外を効率的な方法で格納し、例外にないものにはデフォルトを提供できると考えるかもしれません。残念ながら、これを行う方法にはルックアップのオーバーヘッドが伴い、コストがかかりすぎて効率的ではありません。 (1つの問題として、予期しない分岐が発生した場合に パイプラインストール が発生し、コストが高くなる傾向があることに注意してください。)
Unicodeでは、この問題はエンコーディングに大きく依存することに注意してください。 Javaが作成された場合、Unicodeは64 K以内に収まるため、Javaは1文字あたり2バイトを使用し、文字列の長さは単に分割されたバイト数でした。 (このエンコーディングはUCS-2と呼ばれていました。)これにより、特定の文字にジャンプしたり、特定の部分文字列を抽出したりするのが速くなり、indexOf()
の非効率性は問題になりませんでした。成長したので、Unicode文字はJava文字に必ずしも適合しません。これは、Javaが彼らが回避しようとしていたサイズの問題に陥りました。(そのエンコーディング現在はUTF-16です。)下位互換性のために、Java文字のサイズを変更することはできませんでしたが、Unicode文字とJava =文字は同じものです。違いはありますが、Javaプログラマーはそれを知っており、日常生活で遭遇する可能性はさらに低いです(Windowsと.NETは同じように同じ理由でパス)
他の一部の言語および環境では、代わりにUTF-8が使用されます。 ASCIIは有効なUnicodeであり、Boyer-Mooreが効率的です。トレードオフは、可変バイトの問題に注意を怠ると、実際よりもはるかに明確にヒットすることです。 UTF-16。
それは主にこれに帰着します:最も明白な改善はボイヤー・ムーア、またはそのいくつかの変種によるものです。ただし、B-Mとバリアントは完全に異なるインターフェースを必要としています。
特に、Boyer-Mooreと導関数は実際には2つのステップで機能します。最初に初期化を行います。これは、検索する文字列forにのみ基づいてテーブルを作成します。これにより、必要に応じて何度でもその文字列を検索するために使用できるテーブルが作成されます。
あなたは確かにcouldテーブルをメモし、同じターゲット文字列の後続の検索にそれを使用することにより、これを既存のインターフェースに適合させます。これは、Sunの本来の目的であるこの機能にはあまり適合しないと思います。それは、他のほとんどに依存しない低レベルのビルディングブロックであることです。それをかなり他のインフラストラクチャに依存する高レベルの関数にすることは、(とりわけ)使用したメモ化インフラストラクチャが部分文字列検索を使用できないようにする必要があることを意味します。
その最も可能性の高い結果は、このようなもの(つまり、スタンドアロンの検索ルーチン)を別の名前で単純に再実装し、より高いレベルのルーチンを既存の名前で再実装することだと思います。すべてのことを考慮すると、新しい名前で新しい高レベルのルーチンを作成するほうがおそらく意味があると思います。
それに対する明白な代替策は、ある種の簡略化されたバージョンのメモ化を使用することです。たとえば、静的に1つのテーブルのみを保存し、ターゲット文字列がテーブルの作成に使用されたものと同一である場合にそれを再利用します。 。それは確かに可能ですが、多くのユースケースでは最適とは言えません。スレッドセーフにすることも簡単ではありません。
別の可能性は、B-M検索の2ステップの性質を明示的に公開することです。私は誰もが本当にそのアイデアを気に入っているとは思いません-それはかなり高いコスト(不器用さ、親しみやすさの欠如)を伴い、多くのユースケースにはほとんどまたはまったくメリットがありません(この件に関するほとんどの研究は、平均文字列長が20文字)。