2017-09-25 7 views
0

1か月のログファイルが1つあります。これらのファイルは、以下のスニペットのように、各ラインのいくつかの情報を持つプレーンテキストです:大きなテキストファイル(400万行以上)を読み込み、.NETの各行を解析する

1?2017-06-01T00:00:00^148^3 
2?myVar1^3454.33 
2?myVar2^35 
2?myVar3^0 
1?2017-06-01T00:00:03^148^3 
... 

を処理し、このデータを表示するために、私はこれらのtxtファイルを読み込み、WPFアプリケーションを開発しています、ラインを解析し、このデータを保存SQLiteデータベースに保存します。次に、ユーザーがサブセットのAVGのような基本的な数学演算を行うことを許可します。

これらのファイルは大きすぎます(それぞれ300mb以上400万行以上)ので、私はProcessLineメソッドのメモリ使用に苦労しています。メソッドは決して終了せず、アプリケーションは単独でブレークモードに入ります。

マイコード:

private bool ParseContent(string filePath) 
    { 
     if (string.IsNullOrEmpty(FilePath) || !File.Exists(FilePath)) 
      return false; 

     string logEntryDateTimeTemp = string.Empty; 

     string [] AllLines = new string[5000000]; //only allocate memory here 
     AllLines = File.ReadAllLines(filePath); 
     Parallel.For(0, AllLines.Length, x => 
     { 
      ProcessLine(AllLines[x], ref logEntryDateTimeTemp); 
     }); 

     return true; 
    } 

    void ProcessLine(string line, ref string logEntryDateTimeTemp) 
    { 
     if (string.IsNullOrEmpty(line)) 
      return; 

     var logFields = line.Split(_delimiterChars); 

     switch (logFields[0]) 
     { 
      case "1": 
       logEntryDateTimeTemp = logFields[1]; 
       break; 
      case "2": 
       LogEntries.Add(new LogEntry 
       { 
        Id = ItemsCount + 1, 
        CurrentDateTime = logEntryDateTimeTemp, 
        TagAddress = logFields[1], 
        TagValue = Convert.ToDecimal(logFields[2]) 
       }); 

       ItemsCount++; 
       break; 
      default: 
       break; 
     } 
    } 

はそれを行うための良い方法はありますか?

OBS:

 #region StreamReader 
     //using (StreamReader sr = File.OpenText(filePath)) 
     //{ 
     // string line = String.Empty; 
     // while ((line = sr.ReadLine()) != null) 
     // { 
     //  if (string.IsNullOrEmpty(line)) 
     //   break; 

     //  var logFields = line.Split(_delimiterChars); 

     //  switch (logFields[0]) 
     //  { 
     //   case "1": 
     //    logEntryDateTimeTemp = logFields[1]; 
     //    break; 
     //   case "2": 
     //    LogEntries.Add(new LogEntry 
     //    { 
     //     Id = ItemsCount + 1, 
     //     CurrentDateTime = logEntryDateTimeTemp, 
     //     TagAddress = logFields[1], 
     //     TagValue = Convert.ToDecimal(logFields[2]) 
     //    }); 

     //    ItemsCount++; 
     //    break; 
     //   default: 
     //    break; 
     //  } 
     // } 
     //} 
     #endregion 

     #region ReadLines 
     //var lines = File.ReadLines(filePath, Encoding.UTF8); 

     //foreach (var line in lines) 
     //{ 
     // if (string.IsNullOrEmpty(line)) 
     //  break;  

     // var logFields = line.Split(_delimiterChars); 

     // switch (logFields[0]) 
     // { 
     //  case "1": 
     //   logEntryDateTimeTemp = logFields[1]; 
     //   break; 
     //  case "2": 
     //   LogEntries.Add(new LogEntry 
     //   { 
     //    Id = ItemsCount + 1, 
     //    CurrentDateTime = logEntryDateTimeTemp, 
     //    TagAddress = logFields[1], 
     //    TagValue = Convert.ToDecimal(logFields[2])       
     //   }); 

     //   ItemsCount++; 
     //   break; 
     //  default: 
     //   break; 
     // }    
     //} 
     #endregion 

OBS2::私は、Visual Studio 2017を使用していて、アプリケーションをデバッグモードで実行されている場合、私はあるファイルを読み込むための2つの他の方法を、またテストしてみましたアプリケーションが突然中断モードに入り、次のように出力ウィンドウにメッセージを読み取ります

CLRは60秒間COM コンテキスト0xb544f0にCOMコンテキスト0xb545a8から移行することができませんでした。宛先 コンテキスト/アパートメントを所有するスレッドは、ポンピングすることなく、非常に長い実行中の操作を 処理中である可能性があります。 メッセージ。このような状況は、一般的に負のパフォーマンスの影響を受け、アプリケーションが応答しなくなったり、時間の経過とともにメモリが継続的に累積することさえあります。この問題を回避するには、 シングルスレッドアパートメント(STA)スレッドはすべて、ポンプ待機の プリミティブ(CoWaitForMultipleHandlesなど)を使用し、長時間実行する操作ではメッセージ を定期的に送信する必要があります。

+0

私はいくつかの質問をしていますが、最初にそのファイルがなぜそれほど大きいのですか? –

+2

はい、これを実行する方がはるかに優れています - 一度に1行ずつ読み込み、すべてを一度にメモリに読み込もうとするのではなく、あなたの2番目のアプローチは、テキストファイル全体を読み取ることはありませんが、まだ行にエントリを持つメモリ内のコレクションを構築するようです... –

+0

@JonSkeetそれは、コレクションのサイズがどうにかしてアプリケーションを中断させる原因になる要素の数が高すぎるために多くのメモリを占有していますか? –

答えて

1

あなたは、このコレクションが取得するので、多くのログエントリを持っているので、あなたはおそらく、ProcessLineLogEntries.Addで例外を取得メモリには大きすぎます。

だからあなたはデータベースにすぐにのエントリをリストに追加することなく保存してください。

しかし、1行だけを読んで処理し、次の行を読み、前の行を忘れてしまいます。 File.ReadAllLinesはメモリを占有するstring[]にすべての行を一度に読み込みます(またはOutOfMemoryExceptionを引き起こします)。

代わりにStreamReader os File.ReadLinesを使用できます。

+0

これは私の最初の試み、同じ結果でした。私はコレクションをスキップして、解析されたデータをデータベースに直接保存する方が良いと思っています。 –

+0

@ lucas.mdo:どのコレクションですか?私のコードにはコレクションはありません。 OOM例外もある場合は、行区切り文字のない文字列があります。多分、別の区切り文字を使用して行を区切ります。 –

+1

@ lucas.mdo: 'ProcessLine'の' LogEntries.Add'で例外が発生します。これは理にかなっています。なぜなら、このコリジョンがメモリには大きすぎるログエントリを持っているからです。 –

2

代わりに、一度にファイル全体をメモリにロードするStreamReaderを使用してみてください:

using (System.IO.StreamReader sr = new System.IO.StreamReader(filePath)) 
{ 
    string line; 
    while ((line = sr.ReadLine()) != null) 
    { 
     //.. 
    } 
} 
+0

OBSのセクションで述べたように、私はすでに試してみました。それでも、同じ結果です。 –

+0

私の声明「同じ結果」は不正確です。申し訳ありません。 StreamReaderは適切なアプローチですが、私はコレクションを使って解析されたデータをメモリに保存するのは間違っていました。それが問題でした。 –

1

StreamReaderを使用し、行ごとに読み取る必要があります。それは読書のためのメモリ使用量を減らすでしょう。

また、解析されたレコードの比較的小さなバッファをデータベースに追加するようにしてください。それは約1000レコードかもしれません。コレクションが1000個に達すると、それをデータベースに書き込んでください(一括挿入による単一トランザクションで行うのが理想です)、コレクションをクリーンアップして次の入力ファイルのチャンクに移動してください。

入力ファイルの処理された位置を覚えて、アプリケーションが失敗した場合に最後の点からアプリケーションが再開されるようにするのがよいでしょう。

関連する問題