2017-09-25 2 views
0

각 달의 로그 파일이 하나씩 있습니다. 이 파일은 아래의 조각 같은 각 라인에 약간의 정보와 일반 텍스트입니다큰 텍스트 파일 (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 : 나는 또한있는 파일을 읽기위한 두 가지 다른 방법을 테스트 한 응용 프로그램이 디버그 모드에서 실행 중일 때 나는 비주얼 스튜디오 2017을 사용하고있어 애플리케이션이 갑자기 중단 모드로 진입 한 다음 출력 창에 메시지를 읽

CLR을 60 초 동안 COM에 COM 컨텍스트 0xb545a8에서 컨텍스트 0xb544f0을 전환 할 수 없었다. 대상 컨텍스트/아파트를 소유하는 스레드는 펌핑 대기없이 수행하거나 서버 을 펌핑하지 않고 매우 오래 실행중인 작업을 처리하는 중 가장 가능성이 큽니다. 이 상황은 일반적으로 부정적인 성능 영향 을 가지며 응용 프로그램이 응답하지 않거나 시간이 지남에 따라 계속 사용량이 누적 될 수도 있습니다. 이 문제를 방지하려면 단일 스레드 아파트 (STA) 스레드는 펌핑 대기 프리미티브 (예 : CoWaitForMultipleHandles)를 사용하고 장시간 실행하는 동안 정기적으로 메시지 을 보내야합니다.

+0

두 가지 질문이 있습니다. 그 중 첫 번째로 파일이 너무 큰 이유는 무엇입니까? –

+2

네, 훨씬 더 좋은 방법이 있습니다 - 한 번에 한 줄씩 읽으십시오. 한 번에 모든 것을 메모리로 읽어들이려고하는 것이 아닙니다. 두 번째 접근 방식은 전체 텍스트 파일을 읽지는 않지만 여전히 메모리에 행을 하나씩 저장하는 콜렉션을 구축하는 것 같습니다. –

+0

@JonSkeet 콜렉션의 크기가 어쨌든 응용 프로그램을 중단시키는 원인이 될 수 있습니까? 요소 수가 너무 많아서 많은 메모리를 차지하기 때문에? –

답변

1

당신은 아마 ProcessLineLogEntries.Add에서 예외가,이 컬렉션을 가져 많은 로그 항목을 가지고 있기 때문에 메모리가 너무 크다.

그래서 목록에 추가하지 않고 바로 데이터베이스에 항목을 저장해야합니다 ().

하지만 한 줄만 읽은 다음 처리하고 다음 줄을 읽은 다음 이전 줄을 잊어 버려야합니다. File.ReadAllLines은 메모리를 차지할 string[]으로 모든 라인을 한 번에 읽습니다 (또는 OutOfMemoryException을 발생 시킴).

대신 StreamReader os File.ReadLines을 사용할 수 있습니다.

+0

그건 내 첫 시도, 같은 결과였다. 컬렉션을 건너 뛰고 구문 분석 된 데이터를 데이터베이스에 저장하면 더 좋을지 궁금합니다. –

+0

@ lucas.mdo : 어떤 컬렉션? 내 코드에는 컬렉션이 없습니다. OOM 예외가있는 경우 줄 구분 기호가없는 문자열이 있습니다. 어쩌면 또 다른 구분 기호는 라인을 분리하는 데 사용됩니다 –

+1

@ lucas.mdo : 'ProcessLine'의'LogEntries.Add'에서 예외가 발생합니다. 당신이 너무 많은 로그 항목을 가지고있어서이 colection이 메모리에 비해 너무 크기 때문에 의미가 있습니다. –

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 개의 항목에 도달하면이를 데이터베이스에 쓰거나 (이상적으로 대량 삽입이있는 단일 트랜잭션으로) 콜렉션을 정리하고 다음 입력 파일 청크로 이동해야합니다.

좋은 방법은 입력 파일에서 처리 된 위치를 기억하여 응용 프로그램이 실패한 경우 마지막 지점부터 다시 시작하는지 확인하는 것입니다.

관련 문제