C #에서 큰 파일을 바이트 배열로 읽는 가장 좋은 방법은 무엇입니까?


391

큰 바이너리 파일 (수 메가 바이트)을 바이트 배열로 읽는 웹 서버가 있습니다. 서버가 동시에 여러 파일을 읽을 수 있으므로 (다른 페이지 요청) CPU에 너무 많은 부담을주지 않고이를 수행하는 가장 최적화 된 방법을 찾고 있습니다. 아래 코드가 충분합니까?

public byte[] FileToByteArray(string fileName)
{
    byte[] buff = null;
    FileStream fs = new FileStream(fileName, 
                                   FileMode.Open, 
                                   FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);
    long numBytes = new FileInfo(fileName).Length;
    buff = br.ReadBytes((int) numBytes);
    return buff;
}

60
귀하의 예는로 축약 될 수 있습니다 byte[] buff = File.ReadAllBytes(fileName).
Jesse C. Slicer

3
파일이 타사 웹 서비스 인 이유는 파일이 스트리밍되지 않고 웹 서비스로 전송되기 전에 RAM에 완전히 있어야한다는 것을 의미합니까? 웹 서비스는 차이점을 알지 못합니다.
Brian

@Brian, 일부 클라이언트는 Java와 같은 .NET 스트림을 처리하는 방법을 모릅니다. 이 경우 수행 할 수있는 모든 것은 전체 파일을 바이트 배열로 읽는 것입니다.
sjeffrey

4
@ sjeffrey : 데이터가 .NET 스트림으로 전달되지 않고 스트리밍되어야한다고 말했습니다. 클라이언트는 차이점을 모릅니다.
Brian

답변:


776

전체 내용을 간단히 다음과 같이 바꾸십시오.

return File.ReadAllBytes(fileName);

그러나 메모리 소비가 염려 되면 전체 파일을 메모리로 한 번에 읽지 마십시오 . 청크 단위로 그렇게해야합니다.


40
이 방법은 2 ^ 32 바이트 파일 (4.2 GB)로 제한
마흐무드 Farahat

11
File.ReadAllBytes는 큰 파일을 사용하여 OutOfMemoryException을 발생
시킵니다 (630MB

6
@ juanjo.arana 그래, 물론 ... 항상 기억에 맞지 않는 것이있을 것입니다.이 경우 질문에 대한 답이 없습니다. 일반적으로 파일을 스트리밍하고 메모리에 모두 저장하지 않아야합니다. 이 부분을보고 스톱 갭 측정을 원할 수 있습니다. msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Mehrdad Afshari

4
.NET에는 배열 크기에 제한이 있지만 .NET 4.5에서는 특별한 구성 옵션을 사용하여 큰 배열 (> 2GB)에 대한 지원을 켤 수 있습니다. msdn.microsoft.com/en-us/library/hh285054.aspx
불법 -이민자

3
@harag 아니오, 그것은 질문이하는 것이 아닙니다.
Mehrdad Afshari

72

나는 여기서 대답은 일반적 으로 "하지 말라"고 주장 할 수 있습니다 . 한 번에 모든 데이터 를 절대적으로 필요로 하지 않는 한, Stream기반 API (또는 일부 리더 / 반복기 변형) 사용을 고려하십시오 . 즉 , 특히 시스템 부하를 최소화하고 처리량을 최대화하기 위해 (질문에 의해 제안) 여러 병렬 작업을 할 때 중요합니다.

예를 들어, 발신자에게 데이터를 스트리밍하는 경우 :

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
    byte[] buffer = new byte[2048];
    int bytesRead;
    while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
        dest.Write(buffer, 0, bytesRead);
    }
}

3
명령문에 추가하려면 파일을 클라이언트에 스트리밍하는 것과 같은 I / O 바운드 작업이있는 경우 비동기 ASP.NET 처리기를 고려하는 것이 좋습니다. 그러나 어떤 이유로 전체 파일 읽어야하는 경우 byte[]스트림이나 다른 것을 사용하지 말고 시스템 제공 API를 사용하는 것이 좋습니다.
Mehrdad Afshari

@Mehrdad-동의 함; 그러나 전체 컨텍스트는 명확하지 않습니다. 마찬가지로 MVC에는 이에 대한 조치 결과가 있습니다.
Marc Gravell

예, 한 번에 모든 데이터가 필요합니다. 타사 웹 서비스로 이동합니다.
Tony_Henrich

시스템 제공 API는 무엇입니까?
Tony_Henrich

1
@Tony : 나는 나의 대답에서 언급했다 : File.ReadAllBytes.
Mehrdad Afshari

32

나는 이것을 생각할 것이다 :

byte[] file = System.IO.File.ReadAllBytes(fileName);

3
실제로 큰 파일을 가져올 때 중단 될 수 있습니다.
vapcguy

28

File.ReadAllBytes 대신 코드를 다음과 같이 고려할 수 있습니다.

public byte[] ReadAllBytes(string fileName)
{
    byte[] buffer = null;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, (int)fs.Length);
    }
    return buffer;
} 

Integer.MaxValue-Read 메소드에 의해 배치 된 파일 크기 제한에 유의하십시오. 즉, 한 번에 2GB 청크 만 읽을 수 있습니다.

또한 FileStream에 대한 마지막 인수는 버퍼 크기입니다.

FileStreamBufferedStream 에 대해 읽으십시오 .

항상 가장 빠른 프로파일 링을위한 간단한 샘플 프로그램이 가장 유리합니다.

또한 기본 하드웨어는 성능에 큰 영향을 미칩니다. 대용량 캐시가있는 서버 기반 하드 디스크 드라이브와 온보드 메모리 캐시가있는 RAID 카드를 사용하고 있습니까? 아니면 IDE 포트에 연결된 표준 드라이브를 사용하고 있습니까?


하드웨어 유형이 다른 이유는 무엇입니까? IDE 인 경우 .NET 방법을 사용하고 RAID 인 경우 다른 방법을 사용합니까?
Tony_Henrich

@Tony_Henrich-프로그래밍 언어에서 호출하는 것과는 아무런 관련이 없습니다. 다른 유형의 하드 디스크 드라이브가 있습니다. 예를 들어 Seagate 드라이브는 "AS"또는 "NS"로 분류되며 NS는 서버 기반의 대용량 캐시 드라이브입니다. 여기서 "AS"드라이브는 가정용 컴퓨터 기반 드라이브입니다. 탐색 속도 및 내부 전송률은 디스크에서 무언가를 읽는 속도에 영향을줍니다. RAID 어레이는 캐싱을 통해 읽기 / 쓰기 성능을 크게 향상시킬 수 있습니다. 따라서 파일을 한 번에 읽을 수는 있지만 기본 하드웨어는 여전히 결정적인 요소입니다.

2
이 코드에는 치명적인 버그가 있습니다. 읽기는 최소한 1 바이트 만 반환하면됩니다.
mafu

확인 된 구문으로 다음과 같이 long to int cast를 래핑해야합니다. checked ((int) fs.Length)
tzup

나는 var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);using진술 에서 할 것입니다. 그러나 그것은 OP가 한 것과 효과적으로 같습니다 . 길이 값을 가져 와서 변환 fs.Length하는 int대신 캐스팅 하여 코드 줄을 잘라 냈습니다 . longFileInfo
vapcguy

9

작업 빈도, 파일 크기 및보고있는 파일 수에 따라 고려해야 할 다른 성능 문제가 있습니다. 기억해야 할 것은 가비지 수집기의 도움으로 각 바이트 배열이 해제된다는 것입니다. 해당 데이터를 캐싱하지 않으면 많은 가비지가 발생하여 GC의 % 시간으로 대부분의 성능이 손실 될 수 있습니다. 청크가 85K보다 큰 경우 LOH (Large Object Heap)에 할당하게되며 모든 세대의 컬렉션을 해제해야합니다 (매우 비싸며 서버에서 진행되는 동안 모든 실행이 중지됨) ). 또한 LOH에 많은 개체가있는 경우 LOH 조각화 (LOH가 압축되지 않음)로 인해 성능이 저하되고 메모리 부족 예외가 발생할 수 있습니다. 특정 지점에 도달하면 프로세스를 재활용 할 수 있지만 이것이 최선의 방법인지 모르겠습니다.

요점은 모든 바이트를 메모리에 가장 빨리 읽기 전에 앱의 전체 수명주기를 고려해야한다는 것입니다. 그렇지 않으면 전반적인 성능을 위해 단기 성능을 거래 할 수 있습니다.


그것에 대해 소스 코드를 C #을, 관리를 위해 garbage collector, chunks, 성능, 이벤트 카운터 , ...
PreguntonCojoneroCabrón

6

내가 말하고 싶지만 BinaryReader괜찮지 만, 대신 버퍼의 길이를 취득하기위한 코드의 그 라인을 모든이에 리팩토링 할 수있다 :

public byte[] FileToByteArray(string fileName)
{
    byte[] fileData = null;

    using (FileStream fs = File.OpenRead(fileName)) 
    { 
        using (BinaryReader binaryReader = new BinaryReader(fs))
        {
            fileData = binaryReader.ReadBytes((int)fs.Length); 
        }
    }
    return fileData;
}

주석 처리기 중 하나가 600MB보다 큰 파일에 문제가 .ReadAllBytes()있음을 포함 .ReadAllBytes()하는 최상위 응답에 대한 주석에서을 사용 했기 때문에를 사용하는 것보다 낫습니다 BinaryReader. 또한,에 넣어 using문은 보장 FileStreamBinaryReader폐쇄와 배치된다.


C #의 경우, 위에서 제공된 "(FileStream fs = new File.OpenRead (fileName)) 사용"대신 "(FileStream fs = File.OpenRead (fileName)) 사용"을 사용해야합니다. 방금 File.OpenRead () 전에 새 키워드를 제거
Syed Mohamed

@Syed 위의 코드는 C # 용으로 작성되었지만 new필요하지 않은 것이 맞습니다 . 제거되었습니다.
vapcguy

1

'큰 파일'이 4GB 제한을 초과하는 경우 다음의 서면 코드 논리가 적합합니다. 주목해야 할 주요 문제는 SEEK 방법과 함께 사용되는 LONG 데이터 유형입니다. LONG은 2 ^ 32 데이터 경계를 넘어 가리킬 수 있습니다. 이 예제에서 코드는 먼저 1GB 청크로 큰 파일을 처리하고 있으며, 전체 1GB 청크가 처리 된 후 남은 (<1GB) 바이트가 처리됩니다. 이 코드를 사용하여 4GB 크기를 초과하는 파일의 CRC를 계산합니다. ( 이 예에서 crc32c 계산에 https://crc32c.machinezoo.com/ 사용 )

private uint Crc32CAlgorithmBigCrc(string fileName)
{
    uint hash = 0;
    byte[] buffer = null;
    FileInfo fileInfo = new FileInfo(fileName);
    long fileLength = fileInfo.Length;
    int blockSize = 1024000000;
    decimal div = fileLength / blockSize;
    int blocks = (int)Math.Floor(div);
    int restBytes = (int)(fileLength - (blocks * blockSize));
    long offsetFile = 0;
    uint interHash = 0;
    Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
    bool firstBlock = true;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[blockSize];
        using (BinaryReader br = new BinaryReader(fs))
        {
            while (blocks > 0)
            {
                blocks -= 1;
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(blockSize);
                if (firstBlock)
                {
                    firstBlock = false;
                    interHash = Crc32CAlgorithm.Compute(buffer);
                    hash = interHash;
                }
                else
                {
                    hash = Crc32CAlgorithm.Append(interHash, buffer);
                }
                offsetFile += blockSize;
            }
            if (restBytes > 0)
            {
                Array.Resize(ref buffer, restBytes);
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(restBytes);
                hash = Crc32CAlgorithm.Append(interHash, buffer);
            }
            buffer = null;
        }
    }
    //MessageBox.Show(hash.ToString());
    //MessageBox.Show(hash.ToString("X"));
    return hash;
}

0

C #에서 BufferedStream 클래스를 사용하여 성능을 향상 시키십시오. 버퍼는 데이터를 캐시하는 데 사용되는 메모리의 바이트 블록으로 운영 체제 호출 횟수를 줄입니다. 버퍼는 읽기 및 쓰기 성능을 향상시킵니다.

코드 예제 및 추가 설명은 다음을 참조하십시오. http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx


BufferedStream한 번에 모든 내용을 읽을 때 의 요점은 무엇입니까 ?
Mehrdad Afshari

그는 파일을 한 번에 읽지 않도록 최고의 성능을 요구했습니다.
Todd Moses

9
작업 상황에서 성능을 측정 할 수 있습니다. 한 번에 메모리로 순차적으로 읽는 스트림에 대한 추가 버퍼링은 추가 버퍼의 이점을 얻지 못할 수 있습니다.
Mehrdad Afshari

0

이것을 사용하십시오 :

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;

2
스택 오버플로에 오신 것을 환영합니다! 설명은이 플랫폼에 대한 답변의 중요한 부분이므로 코드와 질문의 문제를 해결하는 방법 및 다른 답변보다 더 나은 이유를 설명하십시오. 우리의 가이드 좋은 답변을 쓰는 ​​방법 이 도움이 될 것입니다. 감사합니다
David

-4

나는 시도 추천 Response.TransferFile()당시로서는 방법 Response.Flush()Response.End()당신의 큰 파일을 서빙합니다.


-7

2GB 이상의 파일을 처리하는 경우 위의 방법이 실패한 것을 알 수 있습니다.

스트림을 MD5로 전달 하고 파일을 청크하는 것이 훨씬 쉽습니다 .

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}

11
코드가 질문과 어떻게 관련이 있는지 (또는 텍스트로 제안한 내용) 보지 못합니다
Vojtech B
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.