web-dev-qa-db-ja.com

あるストリームの内容を別のストリームにコピーする方法は?

あるストリームの内容を別のストリームにコピーするための最善の方法は何ですか?このための標準的な実用的方法はありますか?

500
Anton

.NET 4.5以降から、 Stream.CopyToAsyncメソッドがあります。

input.CopyToAsync(output);

これは Task を返します。これは、次のように完了時に継続できます。

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がスローされます。

671
Nick

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

そして.NET 4.0は、通常のストリームオブジェクトに対して.CopyToを持ちます。

.NET 4.0:

instream.CopyTo(outstream);
63
Joshua

私は以下の拡張方法を使います。 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);
    }
31
Eloff

.NET Framework 4では、System.IO名前空間のStream Classの新しい "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/>");
1
Jayesh Sorathia

「CopyStream」の実装を区別する基本的な質問は次のとおりです。

  • 読み取りバッファのサイズ
  • 書き込みサイズ
  • 複数のスレッドを使用できますか(読み取り中に書き込み)。

これらの質問に対する答えは、CopyStreamの実装が大きく異なることになり、どの種類のストリームを使用していて、何を最適化しようとしているかによって異なります。 「最良の」実装では、ストリームがどの特定のハードウェアに読み書きしているのかを知る必要さえあります。

1
fryguybob

実際には、ストリームコピーを行うための、それほど手間のかからない方法があります。ただし、これはファイル全体をメモリに保存できることを意味することに注意してください。あなたが数百メガバイト以上になるファイルを扱っているなら、これを試して使ってはいけません。

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

注:バイナリデータと文字エンコーディングに関するいくつかの問題もあります。

1
hannasm

残念ながら、本当に簡単な解決策はありません。あなたはそのようなことを試すことができます:

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クラスの実装が異なるという問題は、何も読むべきものがなければ異なる動作をする可能性があります。ローカルハードドライブからファイルを読み込むストリームは、おそらく読み込み操作がディスクからバッファをいっぱいにするのに十分なデータを読み込むまでブロックし、ファイルの終わりに達した場合に返されるデータは少なくなります。一方、ネットワークから読み取ったストリームは、受信するデータが残っていても返されるデータが少なくなる可能性があります。

一般的なソリューションを使用する前に、使用している特定のストリームクラスのドキュメントを必ず確認してください。

0
Tamas Czinege

あなたがニックネームが投稿した他のストリームにストリームをコピーするための手続きが欲しいのであれば、それはリセットされた位置を欠いている、それはあるべきです

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
0
Kronass

回答のどれも1つのストリームから別のものにコピーする非同期の方法をカバーしていないので、これはあるネットワークストリームから別のものにデータをコピーするためにポート転送アプリケーションでうまく使用したパターンです。パターンを強調するための例外処理はありません。

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);
}
0
mdonatas

.NET 3.5以前の場合は、次のことを試してください。

MemoryStream1.WriteTo(MemoryStream2);
0
ntiago

簡単で安全 - 元のソースから新しいストリームを作成します。

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
0
Graham Laight

使用しているストリームの種類によっては、これをより効率的に行う方法があります。片方または両方のストリームをMemoryStreamに変換できる場合は、GetBufferメソッドを使用してデータを表すバイト配列を直接操作できます。これはあなたがfryguybobによって提起されたすべての問題を抽象化するArray.CopyToのようなメソッドを使うことを可能にします。 .NETを信頼して、データをコピーするための最適な方法を知ることができます。

0
Coderer