特定の文字列を検索する必要がある大きなテキストファイルがあります。行ごとに読み取らずにこれを行う高速な方法はありますか?
この方法は、ファイルのサイズ(100 MBを超える)のために非常に低速です。
ファイルのサイズを考えると、本当に事前にファイル全体をメモリに読み込みたいですか?ここでは、行ごとが最善のアプローチである可能性があります。
これは、ストリームを使用して一度に1文字ずつ読み取る私のソリューションです。値全体が見つかるまで、一度に1文字ずつ値を検索するカスタムクラスを作成しました。
ネットワークドライブに保存された100MBのファイルを使用していくつかのテストを実行しましたが、速度はファイルを読み取る速度に完全に依存していました。ファイルがWindowsでバッファリングされている場合、ファイル全体の検索には3秒もかかりませんでした。それ以外の場合は、ネットワーク速度に応じて、7秒から60秒かかる可能性があります。
メモリ内の文字列に対して実行され、一致する文字がなかった場合、検索自体は1秒未満で完了しました。見つかった先頭の文字の多くが一致する場合、検索にさらに時間がかかる可能性があります。
public static int FindInFile(string fileName, string value)
{ // returns complement of number of characters in file if not found
// else returns index where value found
int index = 0;
using (System.IO.StreamReader reader = new System.IO.StreamReader(fileName))
{
if (String.IsNullOrEmpty(value))
return 0;
StringSearch valueSearch = new StringSearch(value);
int readChar;
while ((readChar = reader.Read()) >= 0)
{
++index;
if (valueSearch.Found(readChar))
return index - value.Length;
}
}
return ~index;
}
public class StringSearch
{ // Call Found one character at a time until string found
private readonly string value;
private readonly List<int> indexList = new List<int>();
public StringSearch(string value)
{
this.value = value;
}
public bool Found(int nextChar)
{
for (int index = 0; index < indexList.Count; )
{
int valueIndex = indexList[index];
if (value[valueIndex] == nextChar)
{
++valueIndex;
if (valueIndex == value.Length)
{
indexList[index] = indexList[indexList.Count - 1];
indexList.RemoveAt(indexList.Count - 1);
return true;
}
else
{
indexList[index] = valueIndex;
++index;
}
}
else
{ // next char does not match
indexList[index] = indexList[indexList.Count - 1];
indexList.RemoveAt(indexList.Count - 1);
}
}
if (value[0] == nextChar)
{
if (value.Length == 1)
return true;
indexList.Add(1);
}
return false;
}
public void Reset()
{
indexList.Clear();
}
}
いずれの場合も、すべてのファイルを確認する必要があります。
ルックアップ ラビン-カープ文字列検索 または同様のもの。
検索の最速の方法は Boyer-Mooreアルゴリズム です。この方法では、ファイルからすべてのバイトを読み取る必要はありませんが、バイトへのランダムアクセスが必要です。また、この方法は簡単に実現できます。
これは、文字ごとに読み取る単純な1関数ソリューションです。私にとってはうまくいきました。
/// <summary>
/// Find <paramref name="toFind"/> in <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="TextReader"/> to find <paramref name="toFind"/> in.</param>
/// <param name="toFind">The string to find.</param>
/// <returns>Position within <paramref name="reader"/> where <paramref name="toFind"/> starts or -1 if not found.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="reader"/> is null.</exception>
/// <exception cref="ArgumentException">When <paramref name="toFind"/> is null or empty.</exception>
public int FindString(TextReader reader, string toFind)
{
if(reader == null)
throw new ArgumentNullException("reader");
if(string.IsNullOrEmpty(toFind))
throw new ArgumentException("String to find may not be null or empty.");
int charsRead = -1;
int pos = 0;
int chr;
do
{
charsRead++;
chr = reader.Read();
pos = chr == toFind[pos] ? pos + 1 : 0;
}
while(chr >= 0 && pos < toFind.Length);
int result = chr < 0 ? -1 : charsRead - toFind.Length;
return result < 0 ? -1 : result;
}
お役に立てば幸いです。
必要な制約まで、ファイルからメモリに一度に大量のデータをバッファリングしてから、文字列を検索できます。
これは、ファイルの読み取り数を減らす効果があり、より高速な方法である可能性がありますが、バッファーサイズを高く設定しすぎると、メモリを大量に消費することになります。
検索文字列の最後に到達するまで、検索文字列内の各文字に一致する文字ごとにファイル文字を読み取ることができるはずです。この場合、一致します。読んだ文字が探している文字と一致しない場合は、一致したカウントを0にリセットして、最初からやり直してください。例(****擬似コード/未テスト****):
byte[] lookingFor = System.Text.Encoding.UTF8.GetBytes("hello world");
int index = 0;
int position = 0;
bool matchFound = false;
using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
{
while (fileStream.ReadByte() == lookingFor[index])
{
index++;
if (index == lookingFor.length)
{
matchFound = true;
position = File.position - lookingFor.length;
break;
}
}
}
これは、使用できる多くのアルゴリズムの1つです(ただし、長さチェックで1つずれている可能性があります)。最初の一致のみが検出されるため、whileループを別のループでラップして、複数の一致を検索することをお勧めします。
また、ファイルを1行ずつ読み取る場合に注意する必要があるのは、一致する文字列が行にまたがっている場合、その文字列が見つからないことです。それで問題ない場合は、行ごとに検索できますが、行にまたがる検索文字列が必要な場合は、上記で詳しく説明したようなアルゴリズムを使用する必要があります。
最後に、最高の速度を探している場合は、上記のコードを移行して、 StreamReader またはその他のバッファー付きリーダーを使用することをお勧めします。
ウェイン・コーニッシュがすでに言ったように:行ごとに読むことが最善のアプローチかもしれません。
たとえば、ファイル全体を文字列に読み込んでから正規表現で検索すると、より洗練されたものになる可能性がありますが、大きな文字列オブジェクトが作成されます。
これらの種類のオブジェクトは、ラージオブジェクトヒープ(LOH、85.000バイトを超えるオブジェクトの場合)に格納されるため、問題が発生する可能性があります。これらの大きなファイルの多くを解析し、メモリが制限されている場合(x86)、LOHフラグメンテーションの問題が発生する可能性があります。
=>多くの大きなファイルを解析する場合は1行ずつ読む方が良いです!
プロジェクトでは、毎回同じまたは異なる文字列で異なるファイルを検索する必要がありますか、それとも毎回同じファイルで異なる文字列を検索する必要がありますか?
後者の場合は、ファイルのインデックスを作成できます。ただし、ファイルが頻繁に変更される場合は、インデックスの作成にコストがかかるため、これを行う意味はありません。
全文検索用にファイルにインデックスを付けるには、Lucene.NETライブラリを使用できます。
行ごとの読み取りを高速化したい場合は、キューベースのアプリケーションを作成できます。
1つのスレッドが行を読み取り、スレッドセーフなキューに入れます。 2つ目は文字列を処理できます
SQL Server 2005/2008に貼り付けて、全文検索機能を使用します。
特定の文字列を検索する必要がある大きなテキストファイルがあります。行ごとに読み取らずにこれを行う高速な方法はありますか?
ファイル全体の検索を回避する唯一の方法は、入力を事前に並べ替えまたは整理することです。たとえば、これがXMLファイルであり、これらの検索の多くを実行する必要がある場合、XMLファイルをDOMツリーに解析することは理にかなっています。または、これが単語のリストであり、「aero」という文字で始まるすべての単語を探している場合、同じファイルでそのような検索をたくさん行う場合は、最初に入力全体を並べ替えるのが理にかなっています。 。
ここでの速度の問題は、検索を実行する前にファイルをメモリにロードするのにかかる速度である可能性があります。アプリケーションのプロファイリングを試して、ボトルネックがどこにあるかを確認してください。ファイルをロードしている場合は、ファイルのロードを「チャンク化」して、ファイルが小さなチャンクでストリーミングされ、各チャンクで検索が実行されるようにすることができます。
明らかに、検出される文字列の一部がファイルの最後にある場合、パフォーマンスは向上しません。
特定の文字列のみを探している場合は、行ごとが最良かつ最も効率的なメカニズムだと思います。一方、特にアプリケーションのいくつかの異なるポイントで複数の文字列を検索する場合は、 Lucene.Net を調べてインデックスを作成し、クエリを実行することをお勧めします。インデックス。これが1回限りの実行である場合(つまり、後で同じファイルを再度クエリする必要がない場合)、システムによって自動的にクリーンアップされる一時ファイルにインデックスを作成できます(通常は起動時間。プログラムの終了時に自分で削除できます)。後で同じファイルを再度検索する必要がある場合は、インデックスを既知の場所に保存して、2回目はパフォーマンスを大幅に向上させることができます。