[c#] C #에서 스트림을 사용하여 큰 텍스트 파일 읽기

응용 프로그램의 스크립트 편집기에로드되는 대용량 파일을 처리하는 방법을 알아내는 멋진 작업이 있습니다 ( 빠른 매크로를위한 내부 제품의 VBA 와 같습니다 ). 대부분의 파일은 약 300-400KB로 잘로드됩니다. 그러나 100MB를 초과하면 프로세스에 어려움이 있습니다 (예상대로).

무슨 일이 일어나고 있는지 파일을 읽고 RichTextBox로 밀어 넣은 다음 탐색합니다.이 부분에 대해 너무 걱정하지 마십시오.

초기 코드를 작성한 개발자는 단순히 StreamReader를 사용하여

[Reader].ReadToEnd()

완료하는 데 시간이 꽤 걸릴 수 있습니다.

내 작업은이 코드를 분할하고, 청크 단위로 버퍼로 읽고, 취소 옵션이있는 진행률 표시 줄을 표시하는 것입니다.

몇 가지 가정 :

  • 대부분의 파일은 30-40MB입니다.
  • 파일의 내용은 텍스트 (바이너리 아님)이고 일부는 Unix 형식이고 일부는 DOS입니다.
  • 내용이 검색되면 어떤 터미네이터가 사용되는지 알아냅니다.
  • 리치 텍스트 상자에서 렌더링하는 데 걸리는 시간이로드되면 아무도 걱정하지 않습니다. 텍스트의 초기로드 일뿐입니다.

이제 질문 :

  • StreamReader를 사용한 다음 Length 속성 (ProgressMax)을 확인하고 설정된 버퍼 크기에 대해 Read를 실행 하고 백그라운드 작업자 내부에서 WHILST 를 반복 하여 기본 UI 스레드를 차단하지 않도록 할 수 있습니까? 그런 다음 stringbuilder가 완료되면 메인 스레드로 반환합니다.
  • 내용은 StringBuilder로 이동합니다. 길이를 사용할 수있는 경우 스트림 크기로 StringBuilder를 초기화 할 수 있습니까?

(전문적인 의견으로는) 좋은 아이디어입니까? 이전에 Streams에서 콘텐츠를 읽는 데 몇 가지 문제가 있었는데, 항상 마지막 몇 바이트 또는 무언가를 놓칠 것이기 때문입니다. 그러나 이것이 사실이라면 다른 질문을 할 것입니다.



답변

다음과 같이 BufferedStream을 사용하여 읽기 속도를 향상시킬 수 있습니다.

using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {

    }
}

2013 년 3 월 업데이트

최근에 1GB 크기의 텍스트 파일 (여기에 포함 된 파일보다 훨씬 큼) 읽기 및 처리 (텍스트 검색) 용 코드를 작성했으며 생산자 / 소비자 패턴을 사용하여 상당한 성능 향상을 달성했습니다. 생산자 작업은를 사용하여 텍스트 줄을 읽고 BufferedStream검색을 수행 한 별도의 소비자 작업에 전달했습니다.

이 패턴을 빠르게 코딩하는 데 매우 적합한 TPL Dataflow를 배울 기회로 사용했습니다.

BufferedStream이 더 빠른 이유

버퍼는 데이터를 캐시하는 데 사용되는 메모리의 바이트 블록이므로 운영 체제에 대한 호출 수를 줄입니다. 버퍼는 읽기 및 쓰기 성능을 향상시킵니다. 버퍼는 읽기 또는 쓰기에 사용할 수 있지만 동시에 둘 다 사용할 수는 없습니다. BufferedStream의 Read 및 Write 메서드는 자동으로 버퍼를 유지합니다.

2014 년 12 월 업데이트 : 마일리지가 다를 수 있음

주석에 따라 FileStream은 내부적 으로 BufferedStream을 사용해야합니다 . 이 답변이 처음 제공되었을 때 BufferedStream을 추가하여 상당한 성능 향상을 측정했습니다. 당시 저는 32 비트 플랫폼에서 .NET 3.x를 대상으로했습니다. 현재 64 비트 플랫폼에서 .NET 4.5를 대상으로했지만 개선되지 않았습니다.

관련

생성 된 대용량 CSV 파일을 ASP.Net MVC 작업에서 응답 스트림으로 스트리밍하는 것이 매우 느린 경우를 발견했습니다. 이 인스턴스에서 BufferedStream을 추가하면 성능이 100 배 향상되었습니다. 자세한 내용은 버퍼링되지 않은 출력 매우 느림을 참조하십시오.


답변

이 웹 사이트 에서 성능 및 벤치 마크 통계읽으면 텍스트 파일 을 읽는 가장 빠른 방법 (읽기, 쓰기 및 처리가 모두 다르기 때문에)이 다음 코드 스 니펫임을 알 수 있습니다.

using (StreamReader sr = File.OpenText(fileName))
{
    string s = String.Empty;
    while ((s = sr.ReadLine()) != null)
    {
        //do your stuff here
    }
}

약 9 개의 다른 방법이 모두 벤치마킹되었지만 다른 독자들이 언급 한 것처럼 버퍼링 된 리더수행하는 경우에도 대부분의 경우 앞서 나온 것 같습니다 .


답변

큰 파일이로드되는 동안 진행률 표시 줄을 표시하라는 요청을 받았다고합니다. 사용자가 진정으로 파일 로딩의 정확한 %를보고 싶어하기 때문입니까, 아니면 어떤 일이 일어나고 있다는 시각적 피드백을 원하기 때문입니까?

후자가 사실이면 솔루션이 훨씬 더 간단 해집니다. 그냥 할 reader.ReadToEnd()백그라운드 스레드에서, 대신 적절한 하나의 윤곽 형 진행률 표시 줄을 표시합니다.

제 경험상 이런 경우가 많기 때문에이 점을 올립니다. 데이터 처리 프로그램을 작성할 때 사용자는 확실히 % 완성도에 관심이 있지만, 단순하지만 느린 UI 업데이트의 경우 컴퓨터가 충돌하지 않았 음을 알고 싶어 할 가능성이 더 높습니다. 🙂


답변

바이너리 파일의 경우 내가 찾은 가장 빠른 방법은 이것입니다.

 MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(file);
 MemoryMappedViewStream mms = mmf.CreateViewStream();
 using (BinaryReader b = new BinaryReader(mms))
 {
 }

내 테스트에서는 수백 배 더 빠릅니다.


답변

백그라운드 작업자를 사용하고 제한된 수의 줄만 읽습니다. 사용자가 스크롤 할 때만 자세히 읽어보십시오.

그리고 ReadToEnd ()를 사용하지 마십시오. 그것은 당신이 생각하는 기능 중 하나입니다. “왜 그들이 그것을 만들었습니까?”; 그것은의 스크립트 키디 ‘ 작은 것들로 잘 간다 도우미,하지만 당신이 볼, 그것은 큰 파일 짜증 …

StringBuilder를 사용하라고 말하는 사람들은 MSDN을 더 자주 읽어야합니다.

성능 고려 사항
Concat 및 AppendFormat 메서드는 모두 새 데이터를 기존 String 또는 StringBuilder 개체에 연결합니다. 문자열 개체 연결 작업은 항상 기존 문자열과 새 데이터에서 새 개체를 만듭니다. StringBuilder 개체는 새 데이터의 연결을 수용하기 위해 버퍼를 유지합니다. 공간을 사용할 수있는 경우 새 데이터가 버퍼 끝에 추가됩니다. 그렇지 않으면 새롭고 더 큰 버퍼가 할당되고 원래 버퍼의 데이터가 새 버퍼로 복사 된 다음 새 데이터가 새 버퍼에 추가됩니다. String 또는 StringBuilder 개체에 대한 연결 작업의 성능은 메모리 할당이 발생하는 빈도에 따라 다릅니다.
문자열 연결 작업은 항상 메모리를 할당하는 반면, StringBuilder 연결 작업은 StringBuilder 개체 버퍼가 너무 작아서 새 데이터를 수용 할 수없는 경우에만 메모리를 할당합니다. 따라서 고정 된 수의 String 개체가 연결되는 경우 연결 작업에 String 클래스가 더 적합합니다. 이 경우 개별 연결 작업은 컴파일러에 의해 단일 작업으로 결합 될 수도 있습니다. 임의의 수의 문자열이 연결되는 경우 연결 작업에 StringBuilder 개체가 선호됩니다. 예를 들어 루프가 임의의 수의 사용자 입력 문자열을 연결하는 경우입니다.

즉 , RAM 메모리처럼 작동하도록 하드 디스크 드라이브의 섹션을 시뮬레이션하는 스왑 파일 시스템을 많이 사용하게되는 엄청난 메모리 할당을 의미 하지만 하드 디스크 드라이브는 매우 느립니다.

StringBuilder 옵션은 단일 사용자로 시스템을 사용하는 사람에게는 괜찮아 보이지만 두 명 이상의 사용자가 동시에 대용량 파일을 읽는 경우 문제가 있습니다.


답변

이 정도면 시작하기에 충분합니다.

class Program
{
    static void Main(String[] args)
    {
        const int bufferSize = 1024;

        var sb = new StringBuilder();
        var buffer = new Char[bufferSize];
        var length = 0L;
        var totalRead = 0L;
        var count = bufferSize;

        using (var sr = new StreamReader(@"C:\Temp\file.txt"))
        {
            length = sr.BaseStream.Length;
            while (count > 0)
            {
                count = sr.Read(buffer, 0, bufferSize);
                sb.Append(buffer, 0, count);
                totalRead += count;
            }
        }

        Console.ReadKey();
    }
}


답변

다음 코드 조각을 살펴보십시오. 을 (를) 언급하셨습니다 Most files will be 30-40 MB. 이것은 Intel Quad Core에서 1.4 초에 180MB를 읽는다고 주장합니다.

private int _bufferSize = 16384;

private void ReadFile(string filename)
{
    StringBuilder stringBuilder = new StringBuilder();
    FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);

    using (StreamReader streamReader = new StreamReader(fileStream))
    {
        char[] fileContents = new char[_bufferSize];
        int charsRead = streamReader.Read(fileContents, 0, _bufferSize);

        // Can't do much with 0 bytes
        if (charsRead == 0)
            throw new Exception("File is 0 bytes");

        while (charsRead > 0)
        {
            stringBuilder.Append(fileContents);
            charsRead = streamReader.Read(fileContents, 0, _bufferSize);
        }
    }
}

원본 기사