あるストリームのコンテンツを別のストリームにコピーするにはどうすればよいですか?


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
これが最速の方法ではないことに注意してください。提供されたコードスニペットでは、新しいブロックが読み取られる前に、書き込みが完了するのを待つ必要があります。非同期で読み取りと書き込みを行うと、この待機はなくなります。状況によっては、これによりコピーが2倍速くなります。ただし、コードがはるかに複雑になるため、速度が問題にならない場合は、コードを単純に保ち、この単純なループを使用します。StackOverflowに関するこの質問には、非同期の読み取り/書き込みを示すコードが含まれています。stackoverflow.com/questions/ 1540658 / よろしく、Sebastiaan
Sebastiaan M

16
FWIW、私のテストでは、4096は実際には32Kよりも速いことがわかりました。CLRが特定のサイズにチャンクを割り当てる方法と関係があります。このため、Stream.CopyToの.NET実装は明らかに4096使用しています
ジェフ

1
あなたはCopyToAsyncが実装されたり、私が行ったように変更を加える方法を知りたい場合、それは、「NET Frameworkで並列プログラミングのためのサンプル」にCopyStreamToStreamAsyncとして利用可能だ(私はコピーするバイトの最大数を指定できるようにするために必要な)code.msdn .microsoft.com / ParExtSamples
Michael

1
FIY、最適なバッファサイズiz 81920バイトではなく32768
Alex Zhukovskiy

2
@Jeffの最新のreferecnceSourceは、実際に81920バイトのバッファーを使用していることを示しています。
Alex Zhukovskiy

66

MemoryStreamには.WriteTo(outstream);があります。

.NET 4.0では、通常のストリームオブジェクトに.CopyToがあります。

.NET 4.0:

instream.CopyTo(outstream);

これらの方法を使用しているWebには、多くのサンプルはありません。これはかなり新しいものですか、それともいくつかの制限があるのですか?
GeneS

3
.NET 4.0の新機能だからです。Stream.CopyTo()は、承認された回答と基本的にまったく同じforループを実行します。デフォルトのバッファサイズは4096ですが、大きいバッファを指定するためのオーバーロードもあります。
Michael Edenfield、2011

9
コピー後にストリームを巻き戻す必要があります:instream.Position = 0;
Draykos 2013年

6
入力ストリームを巻き戻すことに加えて、出力ストリームを巻き戻す必要も発見しました。outstream.Position= 0;
JonH 2015年

32

以下の拡張メソッドを使用しています。1つのストリームが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 2010年

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ストリームを作成するため、エンコードの問題の危険性はありません。バイナリデータはほぼ確実にこの方法でコピーするべきではありません。
ケンプͩ

14
「メモリ内のファイル全体」をロードすることは、「あまり重労働ではない」とはほとんど見なされていないと簡単に主張できます。
2012年

これが原因でメモリ不足の例外が発生する
ColacX

これはストリーミングではありませんreader.ReadToEnd()すべてをRAMに置く
ビザン

1

.NET Framework 4では、System.IO名前空間のストリームクラスの新しい「CopyTo」メソッドが導入されています。このメソッドを使用して、1つのストリームを別のストリームクラスの別のストリームにコピーできます。

これがその例です。

    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
一般的な解決策はここで機能します-ニックの答えは素晴らしいものです。もちろん、バッファサイズは任意の選択ですが、32Kは妥当に聞こえます。Nickの解決策はストリームを閉じないことですが、それは所有者に任せてください。
Jon Skeet、

0

使用しているストリームの種類によっては、これをより効率的に行う方法がある場合があります。ストリームの一方または両方をMemoryStreamに変換できる場合は、GetBufferメソッドを使用して、データを表すバイト配列を直接操作できます。これにより、Array.CopyToなどのメソッドを使用して、fryguybobによって引き起こされるすべての問題を抽象化できます。.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.マルティーニョフェルナンデス

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
確かに、最初の書き込みの前に2番目の読み取りが完了した場合は、最初の読み取りから、それが書き出される前に、bufferForWriteの内容を上書きします。
Peter Jeffery、

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.