58

各行を処理する必要があるテキスト ファイルを読み込もうとしています。現時点では、StreamReader を使用して、各行を個別に読んでいます。

運用効率を損なうことなく、LINQ を使用してこれを行うより効率的な方法 (LoC と読みやすさの点で) があるかどうか疑問に思っています。私が見た例では、ファイル全体をメモリにロードしてから処理しています。ただし、この場合、それが非常に効率的であるとは思いません。最初の例では、ファイルは最大で約 50k になり、2 番目の例では、ファイルのすべての行を読み取る必要はありません (通常、サイズは 10k 未満です)。

最近では、これらの小さなファイルはそれほど重要ではないと主張することもできますが、私は、この種のアプローチが非効率的なコードにつながると考えています。

最初の例:

// Open file
using(var file = System.IO.File.OpenText(_LstFilename))
{
    // Read file
    while (!file.EndOfStream)
    {
        String line = file.ReadLine();

        // Ignore empty lines
        if (line.Length > 0)
        {
            // Create addon
            T addon = new T();
            addon.Load(line, _BaseDir);

            // Add to collection
            collection.Add(addon);
        }
    }
}

2 番目の例:

// Open file
using (var file = System.IO.File.OpenText(datFile))
{
    // Compile regexs
    Regex nameRegex = new Regex("IDENTIFY (.*)");

    while (!file.EndOfStream)
    {
        String line = file.ReadLine();

        // Check name
        Match m = nameRegex.Match(line);
        if (m.Success)
        {
            _Name = m.Groups[1].Value;

            // Remove me when other values are read
            break;
        }
    }
}
4

5 に答える 5

95

イテレータブロックを使用すると、LINQベースのラインリーダーを非常に簡単に記述できます。

static IEnumerable<SomeType> ReadFrom(string file) {
    string line;
    using(var reader = File.OpenText(file)) {
        while((line = reader.ReadLine()) != null) {
            SomeType newRecord = /* parse line */
            yield return newRecord;
        }
    }
}

またはジョンを幸せにするために:

static IEnumerable<string> ReadFrom(string file) {
    string line;
    using(var reader = File.OpenText(file)) {
        while((line = reader.ReadLine()) != null) {
            yield return line;
        }
    }
}
...
var typedSequence = from line in ReadFrom(path)
                    let record = ParseLine(line)
                    where record.Active // for example
                    select record.Key;

次に、バッファリングなしで遅延評価されたシーケンスとして、などReadFrom(...)に最適です。Where

OrderByまたは標準を使用する場合GroupByは、データをメモリにバッファリングする必要があることに注意してください。グループ化と集約が必要な場合、「PushLINQ」には、データに対して集約を実行し、データを破棄できるようにするための高度なコードがあります(バッファリングなし)。ジョンの説明はこちらです。

于 2009-08-13T10:45:02.847 に答える
24

EndOfStreamを常にチェックするよりも、行を読み取ってnullかどうかをチェックする方が簡単です。

ただし、MiscUtilLineReaderには、これらすべてを非常に簡単にするクラスもあります。基本的に、ファイルを公開します(または、ファイルに対してLINQを実行できるようにします。したがって、次のようなことができます。Func<TextReader>IEnumerable<string>

var query = from file in Directory.GetFiles("*.log")
            from line in new LineReader(file)
            where line.Length > 0
            select new AddOn(line); // or whatever

の核心LineReaderは、この実装ですIEnumerable<string>.GetEnumerator

public IEnumerator<string> GetEnumerator()
{
    using (TextReader reader = dataSource())
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

ソースの残りのほとんどすべては、柔軟な設定方法を提供しているだけですdataSource(これはですFunc<TextReader>)。

于 2009-08-13T10:45:59.833 に答える
1

IEnumerable<T>:処理中はファイルが開かれるため、解決策に注意する必要があります。

たとえば、MarcGravellの応答では次のようになります。

foreach(var record in ReadFrom("myfile.csv")) {
    DoLongProcessOn(record);
}

ファイルは処理中ずっと開いたままになります。

于 2009-08-13T10:50:08.137 に答える
0

回答ありがとうございます。ファイルから行を読み取るだけでよいので、主にMarcのものに焦点を当てて、混合物を使用することにしました。どこでも分離が必要だと主張できると思いますが、人生は短すぎます!

ファイルを開いたままにしておくことに関しては、コードはデスクトップ アプリケーションの一部であるため、この場合は問題になりません。

最後に、小文字の文字列を使用していることに気付きました。Java では大文字の文字列と大文字でない文字列に違いがあることは知っていますが、C# では小文字の文字列は大文字の文字列への参照に過ぎないと思いましたか?

public void Load(AddonCollection<T> collection)
{
    // read from file
    var query =
        from line in LineReader(_LstFilename)
        where line.Length > 0
        select CreateAddon(line);

    // add results to collection
    collection.AddRange(query);
}

protected T CreateAddon(String line)
{
    // create addon
    T addon = new T();
    addon.Load(line, _BaseDir);

    return addon;
}

protected static IEnumerable<String> LineReader(String fileName)
{
    String line;
    using (var file = System.IO.File.OpenText(fileName))
    {
        // read each line, ensuring not null (EOF)
        while ((line = file.ReadLine()) != null)
        {
            // return trimmed line
            yield return line.Trim();
        }
    }
}
于 2009-08-13T16:21:03.290 に答える