한 스트림의 내용을 다른 스트림으로 어떻게 복사합니까?


521

한 스트림의 내용을 다른 스트림으로 복사하는 가장 좋은 방법은 무엇입니까? 이에 대한 표준 유틸리티 방법이 있습니까?


아마도 더 중요한 것은이 시점에서 어떻게 콘텐츠를 "스트리밍 가능하게"복사합니까? 즉, 대상 스트림을 소비 할 때 소스 스트림 만 복사한다는 것을 의미합니까?
drzaus

답변:


694

.NET 4.5부터는 Stream.CopyToAsync방법이 있습니다.

input.CopyToAsync(output);

Task완료되면 계속할 수있는 a 를 반환합니다 .

await input.CopyToAsync(output)

// Code from here on will be run in a continuation.

호출 위치에 따라 CopyToAsync다음 코드는이를 호출 한 동일한 스레드에서 계속 될 수도 있고 그렇지 않을 수도 있습니다.

SynchronizationContext호출 할 때 캡처 한 것을 await계속이에 실행됩니다 스레드 결정됩니다.

또한이 호출 (및 변경 될 수있는 구현 세부 사항 임)은 여전히 ​​읽기 및 쓰기 시퀀스를 수행합니다 (I / O 완료시 스레드 차단을 낭비하지 않습니다).

.NET 4.0부터는 Stream.CopyTo방법이 있습니다.

input.CopyTo(output);

.NET 3.5 이하

이를 지원하기 위해 프레임 워크에 구운 것은 없습니다. 다음과 같이 내용을 수동으로 복사해야합니다.

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[32768];
    int read;
    while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write (buffer, 0, read);
    }
}

참고 1 :이 방법을 사용하면 진행 상황을보고 할 수 있습니다 (x 지금까지 읽은 바이트 수 ...)
참고 2 : 왜 고정 버퍼 크기를 사용 input.Length합니까? 해당 길이를 사용하지 못할 수 있습니다! 로부터 문서 :

Stream에서 파생 된 클래스가 탐색을 지원하지 않으면 Length, SetLength, Position 및 Seek를 호출하면 NotSupportedException이 발생합니다.


58
가장 빠른 방법은 아닙니다. 제공된 코드 스 니펫에서 새 블록을 읽기 전에 쓰기가 완료 될 때까지 기다려야합니다. 비동기 적으로 읽기 및 쓰기를 수행하면이 대기가 사라집니다. 어떤 상황에서는 복사 속도가 두 배 빨라집니다. 그러나 속도가 문제가되지 않으면 코드를 훨씬 더 복잡하게 만들므로 간단하게 유지 하고이 간단한 루프를 사용하십시오. 에 StackOverflow에이 질문은 비동기 읽기 / 쓰기를 보여 일부 코드가 있습니다 stackoverflow.com/questions/1540658/... 감사합니다 Sebastiaan
Sebastiaan M

16
FWIW, 내 테스트에서 4096이 실제로 32K보다 빠르다는 것을 알았습니다. CLR이 특정 크기의 청크를 할당하는 방법과 관련이 있습니다. 이 때문에 Stream.CopyTo의 .NET 구현은 분명히 4096을 사용합니다.
Jeff

1
당신 사항 copyToAsync 구현 또는 내가했던 것처럼 수정을하는 방법을 알고 싶다면 다음 "는 .NET 프레임 워크 병렬 프로그래밍에 대한 샘플"에 CopyStreamToStreamAsync로 사용할 수 (I 복사 할 최대 바이트의 수를 지정할 수 있도록 필요한) code.msdn .microsoft.com / ParExtSamples
Michael

1
FIY, 최적의 버퍼 크기 iz 81920바이트가 아님32768
Alex Zhukovskiy

2
@Jeff 최신 referecnceSource의 방송 실제로 81,920 바이트 버퍼를 사용하는 것이다.
Alex Zhukovskiy

66

MemoryStream은 .WriteTo (outstream);

.NET 4.0에는 일반 스트림 객체에 .CopyTo가 있습니다.

.NET 4.0 :

instream.CopyTo(outstream);

이 방법을 사용하는 웹에는 많은 샘플이 표시되지 않습니다. 그것들이 상당히 새롭거나 제한이 있습니까?
GeneS

3
.NET 4.0의 새로운 기능이기 때문입니다. Stream.CopyTo ()는 기본적으로 승인 된 응답과 동일한 for 루프를 정확히 수행하며 추가 무결성 검사를 수행합니다. 기본 버퍼 크기는 4096이지만 더 큰 버퍼를 지정하기위한 과부하도 있습니다.
Michael Edenfield

9
복사 후 스트림을 되 감아 야합니다. instream.Position = 0;
Draykos

6
입력 스트림을 되 감는 것 외에도 출력 스트림을 되 감는 것이 필요하다는 것을 알았습니다. outstream.Position = 0;
JonH

32

다음 확장 방법을 사용합니다. 하나의 스트림이 MemoryStream 인 경우에 대해 과부하를 최적화했습니다.

    public static void CopyTo(this Stream src, Stream dest)
    {
        int size = (src.CanSeek) ? Math.Min((int)(src.Length - src.Position), 0x2000) : 0x2000;
        byte[] buffer = new byte[size];
        int n;
        do
        {
            n = src.Read(buffer, 0, buffer.Length);
            dest.Write(buffer, 0, n);
        } while (n != 0);           
    }

    public static void CopyTo(this MemoryStream src, Stream dest)
    {
        dest.Write(src.GetBuffer(), (int)src.Position, (int)(src.Length - src.Position));
    }

    public static void CopyTo(this Stream src, MemoryStream dest)
    {
        if (src.CanSeek)
        {
            int pos = (int)dest.Position;
            int length = (int)(src.Length - src.Position) + pos;
            dest.SetLength(length); 

            while(pos < length)                
                pos += src.Read(dest.GetBuffer(), pos, length - pos);
        }
        else
            src.CopyTo((Stream)dest);
    }

1

"CopyStream"의 구현을 차별화하는 기본 질문은 다음과 같습니다.

  • 판독 버퍼의 크기
  • 쓰기의 크기
  • 하나 이상의 스레드를 사용할 수 있습니까 (읽는 동안 쓰기).

이 질문에 대한 답변은 CopyStream의 구현 방식이 크게 다르며 어떤 종류의 스트림과 최적화하려는 스트림에 따라 다릅니다. "최상의"구현은 스트림이 읽고 쓰는 특정 하드웨어를 알아야합니다.


1
... 또는 최상의 구현에는 버퍼 크기, 쓰기 크기 및 스레드 허용 여부를 지정할 수있는 과부하가있을 수 있습니까?
MarkJ

1

실제로 스트림 복사를 수행하는 덜 무거운 방법이 있습니다. 그러나 이는 전체 파일을 메모리에 저장할 수 있음을 의미합니다. 주의없이 수백 메가 바이트 이상의 파일로 작업하는 경우에는 사용하지 마십시오.

public static void CopyStream(Stream input, Stream output)
{
  using (StreamReader reader = new StreamReader(input))
  using (StreamWriter writer = new StreamWriter(output))
  {
    writer.Write(reader.ReadToEnd());
  }
}

참고 : 이진 데이터 및 문자 인코딩과 관련하여 일부 문제가있을 수도 있습니다.


6
StreamWriter의 기본 생성자는 BOM ( msdn.microsoft.com/en-us/library/fysy0a4b.aspx ) 없이 UTF8 스트림을 생성 하므로 인코딩 문제가 발생할 위험이 없습니다. 이진 데이터는 거의 이런 방식으로 복사해서는 안됩니다.
kͩeͣmͮpͥ ͩ 2016

14
"메모리에 전체 파일"을로드하는 것이 "무겁지 않은"것으로 여겨지지 않는다고 쉽게 주장 할 수 있습니다.
Seph

이 때문에 메모리 부족 예외가 발생합니다
ColacX

스트리밍 할 스트림 이 아닙니다 . reader.ReadToEnd()RAM에 모든 것을 넣습니다
Bizhan

1

.NET Framework 4에는 System.IO 네임 스페이스의 스트림 클래스에 대한 새로운 "CopyTo"메서드가 도입되었습니다. 이 방법을 사용하면 한 스트림을 다른 스트림 클래스의 다른 스트림으로 복사 할 수 있습니다.

이에 대한 예는 다음과 같습니다.

    FileStream objFileStream = File.Open(Server.MapPath("TextFile.txt"), FileMode.Open);
    Response.Write(string.Format("FileStream Content length: {0}", objFileStream.Length.ToString()));

    MemoryStream objMemoryStream = new MemoryStream();

    // Copy File Stream to Memory Stream using CopyTo method
    objFileStream.CopyTo(objMemoryStream);
    Response.Write("<br/><br/>");
    Response.Write(string.Format("MemoryStream Content length: {0}", objMemoryStream.Length.ToString()));
    Response.Write("<br/><br/>");

알림 : 사용 CopyToAsync()을 권장합니다.
Jari Turkia

0

불행히도 실제로 간단한 해결책은 없습니다. 당신은 그런 것을 시도 할 수 있습니다 :

Stream s1, s2;
byte[] buffer = new byte[4096];
int bytesRead = 0;
while (bytesRead = s1.Read(buffer, 0, buffer.Length) > 0) s2.Write(buffer, 0, bytesRead);
s1.Close(); s2.Close();

그러나 읽을 클래스가 없으면 Stream 클래스의 다른 구현이 다르게 동작 할 수 있습니다. 로컬 하드 드라이브에서 파일을 읽는 스트림은 읽기 작업이 디스크에서 충분한 데이터를 읽어 버퍼를 채우고 파일 끝에 도달 할 때 더 적은 데이터 만 반환 할 때까지 차단 될 것입니다. 반면, 네트워크에서 읽는 스트림은 수신 할 데이터가 더 있어도 더 적은 데이터를 반환 할 수 있습니다.

일반 솔루션을 사용하기 전에 항상 사용중인 특정 스트림 클래스의 문서를 확인하십시오.


5
일반적인 해결책은 여기에서 작동합니다. Nick의 대답은 훌륭합니다. 버퍼 크기는 물론 임의의 선택이지만 32K 사운드는 합리적입니다. Nick의 솔루션은 스트림을 닫지 않는 것이 옳다고 생각합니다. 소유자에게 맡기십시오.
Jon Skeet

0

작업중인 스트림 종류에 따라이 작업을보다 효율적으로 수행 할 수있는 방법이있을 수 있습니다. 스트림 중 하나 또는 둘 모두를 MemoryStream으로 변환 할 수있는 경우 GetBuffer 메서드를 사용하여 데이터를 나타내는 바이트 배열로 직접 작업 할 수 있습니다. 이를 통해 fryguybob이 제기 한 모든 문제를 추상화하는 Array.CopyTo와 같은 메소드를 사용할 수 있습니다. .NET을 신뢰하면 데이터를 복사하는 최적의 방법을 알 수 있습니다.


0

닉이 게시 된 다른 스트림으로 스트림을 복사하기위한 절차를 원하지만 위치 재설정이 누락 된 경우

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[32768];
    long TempPos = input.Position;
    while (true)    
    {
        int read = input.Read (buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write (buffer, 0, read);
    }
    input.Position = TempPos;// or you make Position = 0 to set it at the start
}

그러나 런타임에 절차를 사용하지 않으면 메모리 스트림을 사용해야합니다.

Stream output = new MemoryStream();
byte[] buffer = new byte[32768]; // or you specify the size you want of your buffer
long TempPos = input.Position;
while (true)    
{
    int read = input.Read (buffer, 0, buffer.Length);
    if (read <= 0)
        return;
    output.Write (buffer, 0, read);
 }
    input.Position = TempPos;// or you make Position = 0 to set it at the start

3
모든 스트림이 무작위 액세스를 허용하지는 않기 때문에 입력 스트림의 위치를 ​​변경해서는 안됩니다. 예를 들어 네트워크 스트림에서 위치를 변경할 수없고 읽기 및 / 또는 쓰기 만 할 수 있습니다.
R. Martinho Fernandes

0

어떤 대답도 한 스트림에서 다른 스트림으로 비동기 복사하는 방법을 다루지 않았으므로 다음은 포트 전달 응용 프로그램에서 한 네트워크 스트림에서 다른 네트워크 스트림으로 데이터를 복사하는 데 성공적으로 사용한 패턴입니다. 패턴을 강조하기 위해 예외 처리가 부족합니다.

const int BUFFER_SIZE = 4096;

static byte[] bufferForRead = new byte[BUFFER_SIZE];
static byte[] bufferForWrite = new byte[BUFFER_SIZE];

static Stream sourceStream = new MemoryStream();
static Stream destinationStream = new MemoryStream();

static void Main(string[] args)
{
    // Initial read from source stream
    sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}

private static void BeginReadCallback(IAsyncResult asyncRes)
{
    // Finish reading from source stream
    int bytesRead = sourceStream.EndRead(asyncRes);
    // Make a copy of the buffer as we'll start another read immediately
    Array.Copy(bufferForRead, 0, bufferForWrite, 0, bytesRead);
    // Write copied buffer to destination stream
    destinationStream.BeginWrite(bufferForWrite, 0, bytesRead, BeginWriteCallback, null);
    // Start the next read (looks like async recursion I guess)
    sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}

private static void BeginWriteCallback(IAsyncResult asyncRes)
{
    // Finish writing to destination stream
    destinationStream.EndWrite(asyncRes);
}

4
두 번째 읽기가 첫 번째 쓰기 전에 완료되면 첫 번째 읽기에서 bufferForWrite의 내용을 쓰기 전에 덮어 씁니다.
피터 제프리

0

.NET 3.5 및 이전 버전의 경우 :

MemoryStream1.WriteTo(MemoryStream2);

MemoryStreams를 다루는 경우에만 작동합니다.
Nyerguds

0

쉽고 안전-원본 소스에서 새로운 스트림 만들기 :

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.