C#でストリームをファイルに保存するにはどうすればよいですか?


713

StreamReaderストリームで初期化したオブジェクトがありますが、このストリームをディスクに保存したいと思います(ストリームは.gifまたは.jpgまたはの場合があります.pdf)。

既存のコード:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. これをディスクに保存する必要があります(ファイル名があります)。
  2. 将来的には、これをSQL Serverに保存する必要があるかもしれません。

SQL Serverに保存する場合に必要となるエンコードタイプもありますが、正しいですか?


1
myOtherObjectとは何ですか?
anhtv13

2
この質問に対する回答はまだ受け入れられていませんか?
Brett Rigby

D:@BrettRigbyジョンスキートの回答があります、それはかなり自動受け入れます
リカルド・ディアス・モライス

回答:


913

Jon Skeetの回答でTilendorによって強調されているように、ストリームにはCopyTo.NET 4以降のメソッドがあります。

var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();

またはusing構文:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}

67
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)まだ開始していない場合、またはストリーム全体をコピーしない場合は、呼び出す必要があることに注意してください。
Steve Rukuts、2012年

3
この入力ストリームがhttp接続から取得された場合、バッファリングしてダウンロードし、ソースからすべてのバイトを書き込みますか?????
dbw

2
ストリームをバインドし、同じストリームを使用してPDFファイルを保存すると、「Seek(0、SeekOrigin.Begin)」を使用せずに、ストリームを使用するPDFビューアを作成しました。正しいドキュメントを保存できません。したがって、この「Seek(0、SeekOrigin.Begin)」について言及する場合は+1
user2463514

myOtherObject.InputStream.CopyTo(fileStream); この行はエラーになります:アクセスが拒否されました。
スルハディン

2
myOtherObject ??
ハリー

531

あなたはしてはならない使用StreamReader(GIFやJPGのみのような)バイナリ・ファイルのために。テキストデータStreamReaderです。任意のバイナリデータに使用すると、ほぼ確実にデータ失われます。(Encoding.GetEncoding(28591)を使用している場合は、おそらく大丈夫ですが、ポイントは何ですか?)

なぜあなたStreamReaderはまったく使用する必要があるのですか?バイナリデータバイナリデータとして保持し、それをディスク(またはSQL)にバイナリデータとして書き戻すだけではどうですか。

編集:これは人々が見たいもののようです... あるストリームを別のストリームに(たとえばファイルに)コピーしたいだけの場合、次のようなものを使用してください:

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

ストリームをファイルにダンプするために使用するには、例えば:

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}

Stream.CopyTo.NET 4で導入された、基本的に同じ目的を果たします。


6
これはそのような一般的なケースのようですが、.NETにないことに驚いています。ファイル全体のサイズのバイト配列を作成している人がいるので、大きなファイルで問題が発生する可能性があります。
Tilendor

81
@Tilendor:.NET 4の拡張メソッドとして存在します。(CopyTo)
Jon Skeet

33
拡張メソッドだとは思いませんが、Streamクラスの新機能です。
Kugel

9
@Kugel:ごめんなさい。私は、ユーティリティライブラリに拡張メソッドとしてそれを持っていたが、今では、ストリーム自体にだと、私の拡張メソッドが呼び出されません。
Jon Skeet

4
@フローリアン:それはかなり恣意的です-あまりにも多くのメモリを必要としないように十分に小さい値で、一度に妥当なチャンクを転送するのに十分な大きさ。たぶん16K、32Kでも問題ありません-大きなオブジェクトのヒープに達しないように注意してください。
Jon Skeet、2013年

77
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}

28
おそらくstreamオブジェクトをusing(){}ブラケットに入れるべきではありません。あなたのメソッドはストリームを作成しなかったので、それを破棄すべきではありません。
LarsTech 2013

2
代わりFileStreamに、使用する代わりに置く必要があります。そうしないと、ガベージコレクションされるまで開いたままになります。
Pavel Chikulaev 2014年

あなたのアプローチは、AWS S3クラスゲートウェイクラスを使用してWinFormsでの問題を解決するのに非常に近いことがわかりました ありがとうございました!
Luiz Eduardo

2
これは問題なく実行されましたが、0 KBの出力が得られました。代わりに、正しい出力を得るためにこれを行わなければなりませんでしたFile.WriteAllBytes(destinationFilePath, input.ToArray());。私の場合、inputMemoryStream内からのものZipArchiveです。
SNag 2016年

23
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}

1
これは問題なく実行されましたが、0 KBの出力が得られました。代わりに、正しい出力を得るためにこれを行わなければなりませんでしたFile.WriteAllBytes(destinationFilePath, input.ToArray());。私の場合、inputMemoryStream内からのものZipArchiveです。
SNag 2016年

2
これは、自分が間違っていたことを理解するのに役立ちました。しかし、ストリームの先頭に移動することを忘れないでください: stream.Seek(0, SeekOrigin.Begin);
ネイサン手形

9

CopyToアプリを使用するシステムが.NET 4.0以降にアップグレードされていない可能性があるため、を使用してすべての回答を得ることができません。人々に強制的にアップグレードさせたいと思う人もいますが、互換性も素晴らしいです。

もう1つ、ストリームを使用して最初から別のストリームからコピーすることはありません。なぜそうしないのですか?

byte[] bytes = myOtherObject.InputStream.ToArray();

バイトを取得したら、簡単にファイルに書き込むことができます。

public static void WriteFile(string fileName, byte[] bytes)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
    {
        fs.Write(bytes, 0, (int)bytes.Length);
        //fs.Close();
    }
}

このコードは、.jpgファイルでテストしたとおりに機能しますが、小さいファイル(1 MB未満)でのみ使用したことは認めています。1つのストリーム、ストリーム間でのコピー、エンコーディングの必要なし、バイトの書き込みのみ!StreamReader既にbytes直接ストリームに変換できるストリームがある場合、で複雑化する必要はありません.ToArray()

この方法で私が見ることができる潜在的な欠点は、大きなファイルがあり、それをストリームとして持ち、バイト配列を使用して1つずつバイトを読み取る代わりに、.CopyTo()それと同等またはFileStreamそれを使用してストリーミングできることです。結果として、この方法で実行するのが遅くなる可能性があります。しかし.Write()FileStreamハンドルのメソッドがバイトを書き込むので、チョークするべきではなく、一度に1バイトしか実行しないため、ストリームをaとして保持するのに十分なメモリが必要でbyte[]あることを除いて、メモリを詰まらせませんオブジェクト。これを使用した私の状況では、を取得しOracleBlob、に移動する必要がありましたbyte[]。それは十分に小さかっただけでなく、いずれにせよ、ストリーミングを利用できなかったため、上記の関数にバイトを送信しました。

ストリームを使用する別のオプションCopyStreamは、別の投稿にあるJon Skeetの関数と一緒に使用することです。これはFileStream、入力ストリームを受け取り、そこから直接ファイルを作成するために使用します。File.Create彼のようには使用しません(最初は問題があるように見えましたが、後でVSのバグである可能性が高いことがわかりました...)。

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

public static void WriteFile(string fileName, Stream inputStream)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
    {
        CopyStream(inputStream, fs);
    }

    inputStream.Close();
    inputStream.Flush();
}

1
次のClose理由で電話する必要はありませんusing()
Alex78191 2017年

@ Alex78191について話している場合はinputStream.Close()、もう一度見てください inputStream。変数として送信されます。using上にあるpath+filename出力ストリーム。あなたがfs.Close()の真ん中で話していたらusing、申し訳ありませんが、あなたはそれについて正しく、私はそれを削除しました。
vapcguy 2017

8
//If you don't have .Net 4.0  :)

public void SaveStreamToFile(Stream stream, string filename)
{  
   using(Stream destination = File.Create(filename))
      Write(stream, destination);
}

//Typically I implement this Write method as a Stream extension method. 
//The framework handles buffering.

public void Write(Stream from, Stream to)
{
   for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
      to.WriteByte( (byte) a );
}

/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings 
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/

16
ストリームのバイト単位のコピー(ReadByte / WriteByteを使用)は、バッファー単位のコピー(Read(byte []、int、int)/ Write(byte []、int、int)を使用)よりもはるかに遅くなります。
ケビン

6

FileStreamオブジェクトを使用しないのはなぜですか?

public void SaveStreamToFile(string fileFullPath, Stream stream)
{
    if (stream.Length == 0) return;

    // Create a FileStream object to write a stream to a file
    using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
    {
        // Fill the bytes[] array with the stream data
        byte[] bytesInStream = new byte[stream.Length];
        stream.Read(bytesInStream, 0, (int)bytesInStream.Length);

        // Use FileStream object to write to the specified file
        fileStream.Write(bytesInStream, 0, bytesInStream.Length);
     }
}

46
入力ストリームが1GBの長さの場合-このコードは1GBのバッファを
割り当てよ

1
長さが不明なため、これはResponseStreamでは機能しません。
Tomas Kubes 2013年

あなたがに利用できるメモリを持っている必要があるのは事実ですがbyte[]、1 GB以上のblobをファイルにストリーミングすることはまれだと思います... DVDの急流を保存するサイトがない限り... 、とにかく、ほとんどのコンピューターは最近、少なくとも2 GBのRAMを使用できます。警告は有効ですが、これはほとんどのジョブでおそらく「十分」なケースだと思います。
vapcguy 2016年

Webサーバーは、一度に1人のユーザーしかアクティブにしない限り、このようなケースをまったく許容しません。
NateTheGreatt

6

別のオプションは、ストリームをaに取得してbyte[]使用することFile.WriteAllBytesです。これは行う必要があります:

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}

それを拡張メソッドでラップすると、より適切な名前が付けられます。

public void WriteTo(this Stream input, string file)
{
    //your fav write method:

    using (var stream = File.Create(file))
    {
        input.CopyTo(stream);
    }

    //or

    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    //whatever that fits.
}

3
入力が大きすぎると、メモリ不足の例外が発生します。入力ストリームからファイルストリームにコンテンツをコピーするオプションははるかに優れています
Ykok

4
public void testdownload(stream input)
{
    byte[] buffer = new byte[16345];
    using (FileStream fs = new FileStream(this.FullLocalFilePath,
                        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
             fs.Write(buffer, 0, read);
        }
    }
}

バッファリングされた入力ストリームを直接にFileStream-いいですね!
vapcguy 2016年

3

idisposableの適切な使用と実装を使用する例を次に示します。

static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
    using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
    {
        using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
        {
            while (sourceFileStream.Position < sourceFileStream.Length)
            {
                destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
            }
        }
    }
}

...そしてこれもあります

    public static void WriteToFile(FileStream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
    {
        using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
        {
            while (stream.Position < stream.Length) 
            {
                destinationFileStream.WriteByte((byte)stream.ReadByte());
            }
        }
    }

重要なのは、適切な使用法(上記のようにidisposableを実装するオブジェクトのインスタンス化で実装する必要があります)を理解し、プロパティがストリームでどのように機能するかを理解することです。位置は、文字通り、ストリーム内のインデックス(0から始まります)であり、readbyteメソッドを使用して各バイトが読み取られるときに追跡されます。この場合、私は本質的にそれをforループ変数の代わりに使用し、単純に、ストリーム全体の最後(バイト単位)の長さまでたどるだけです。それは事実上同じであり、すべてをきれいに解決するこのようなシンプルでエレガントなものになるので、バイト単位で無視してください。

また、ReadByteメソッドはプロセス内でバイトをintにキャストするだけで、変換して戻すことができることにも注意してください。

大規模な過負荷を防ぐために順次データ書き込みを確実にするために、ソートの動的バッファーを作成するために最近書いた別の実装を追加します

private void StreamBuffer(Stream stream, int buffer)
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        var memoryBuffer = memoryStream.GetBuffer();

        for (int i = 0; i < memoryBuffer.Length;)
        {
            var networkBuffer = new byte[buffer];
            for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
            {
                networkBuffer[j] = memoryBuffer[i];
                i++;
            }
            //Assuming destination file
            destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
        }
    }
}

説明はかなり単純です。書き込みたいデータのセット全体を覚えておく必要があること、および特定の量だけを書き込みたいことを知っているので、最後のパラメーターを空にして最初のループを行います(これまでと同じです)。 )。次に、渡されたサイズに設定されたバイト配列バッファーを初期化し、2番目のループでjをバッファーのサイズおよび元のサイズと比較し、それが元のサイズより大きいかどうかを調べます。バイト配列、実行を終了します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.