天天看点

分段输出到同一个Stream(.NET)

 某项目需要输出一个数据文件,该文件由2部分组成,即文件头信息和数据。

  项目是使用C#语言在.NET Framework 4上创建的。

  拿到这个需求,首先想到的是定义一个Writer类,在写入方法中创建一个文件流,使用BinaryWriter封装,写入所需要的各种数据。看起来就像这样:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<code>public</code> <code>void</code> <code>Write()</code>

<code>{</code>

<code>    </code><code>using</code> <code>(Stream stream = OpenFileStream())</code>

<code>    </code><code>using</code> <code>(BinaryWriter writer = </code><code>new</code> <code>BinaryWriter(stream))</code>

<code>    </code><code>{</code>

<code>        </code><code>WriteHeader(writer);</code>

<code>        </code><code>WriteData(writer);</code>

<code>    </code><code>}</code>

<code>}</code>

<code>private</code> <code>void</code> <code>WriteHeader(BinaryWriter writer)</code>

<code>    </code><code>// ......</code>

<code>private</code> <code>void</code> <code>WriteData(BinaryWriter writer)</code>

  不过随后需求就发生了变化,因为数据敏感,需要加密。于是想到对文件头部分使用BinaryWriter写入,而后面的数据部分,先使用CryptoStream包装流,再使用BinaryWriter写入。于是改成这样:

<code>        </code><code>using</code> <code>(BinaryWriter headerWriter = </code><code>new</code> <code>BinaryWriter(stream))</code>

<code>        </code><code>{</code>

<code>            </code><code>WriteHeader(headerWriter);</code>

<code>        </code><code>}</code>

<code>        </code><code>using</code> <code>(CryptoStream cryptoStream</code>

<code>            </code><code>= </code><code>new</code> <code>CryptoStream(stream, GetCryptoTransform(),</code>

<code>                </code><code>CryptoStreamMode.Write))</code>

<code>        </code><code>using</code> <code>(BinaryWriter dataWriter = </code><code>new</code> <code>BinaryWriter(cryptoStream))</code>

<code>            </code><code>WriteData(dataWriter);</code>

  改过之后,问题产生了——在使用headerWriter的using语句结束时,会自动调用headerWriter.Dispose(),而这个方法会调用BaseStream.Close(),也就是说,文件流被关闭了,那么后面尝试写入数据时就会抛出异常。

  虽然BinaryWriter有一个构造方法可以申明不关闭流:

<code>public</code> <code>BinaryWriter(</code>

<code>    </code><code>Stream output,</code>

<code>    </code><code>Encoding encoding,</code>

<code>    </code><code>bool</code> <code>leaveOpen</code>

<code>)</code>

  但这个构造方法是在.NET 4.5才加入的,项目是用的4.0的Framework,所以必须另外想办法。而且后面的CryptoStream也存在同样的问题,而它可没有提供不关闭流的构造。

  这里可以想到两个办法来处理:

  1. 在所有内容都写完之后再统一Dispose各种操作对象和流对象。

  2. 定义一个从Stream继承的StreamWrapper,将Close和Dispose都重载并实现为空方法,再定义一个ReallyClose方法来真正关闭封装的流。

  使用第1种方法,就像这样:

<code>    </code><code>using</code> <code>(Stream s = OpenFileStream())</code>

<code>    </code><code>using</code> <code>(BinaryWriter headWriter = </code><code>new</code> <code>BinaryWriter(s))</code>

<code>    </code><code>using</code> <code>(CryptoStream cs = </code><code>new</code> <code>CryptoStream(s, GetCryptoTransform(),</code>

<code>        </code><code>CryptoStreamMode.Write))</code>

<code>    </code><code>using</code> <code>(BinaryWriter dataWriter = </code><code>new</code> <code>BinaryWriter(cs))</code>

<code>        </code><code>WriterHeader(headWriter);</code>

<code>        </code><code>WriteData(dataWriter);</code>

  而且如果一个文件分成了很多很多段的话,这个using列表就太长了。但这不是问题,问题是如果WriteHeader中抛出异常,那么cs和dataWriter这两个对象就浪费了。所以,可以考虑使用try {...} finally {...} 来实现using,

18

19

20

21

22

23

<code>    </code><code>Stream stream = </code><code>null</code><code>;</code>

<code>    </code><code>BinaryWriter headWriter = </code><code>null</code><code>;</code>

<code>    </code><code>CryptoStream cs = </code><code>null</code><code>;</code>

<code>    </code><code>BinaryWriter dataWriter = </code><code>null</code><code>;</code>

<code>    </code><code>try</code>

<code>        </code><code>stream = OpenFileStream();</code>

<code>        </code><code>headWriter = </code><code>new</code> <code>BinaryWriter(stream);</code>

<code>        </code><code>cs = </code><code>new</code> <code>CryptoStream(stream, GetCryptoTransform(), CryptoStreamMode.Write);</code>

<code>        </code><code>dataWriter = </code><code>new</code> <code>BinaryWriter(cs);</code>

<code>    </code><code>finally</code>

<code>        </code><code>if</code> <code>(dataWriter != </code><code>null</code><code>) { dataWriter.Dispose(); }</code>

<code>        </code><code>if</code> <code>(cs != </code><code>null</code><code>) { cs.Dispose(); }</code>

<code>        </code><code>if</code> <code>(headWriter != </code><code>null</code><code>) { headWriter.Dispose(); }</code>

<code>        </code><code>if</code> <code>(stream != </code><code>null</code><code>) { stream.Dispose(); }</code>

  解决问题,但代码量大,而且容易出错。比如,要记得在WriterHeader里面Flush,这个原本会在Dispose()自动执行的操作(对于CryptoStream,需要执行FlushFinalBlock())。

  相对来说,写一个StreamWrapper靠谱多了。不过使用RealClose就失去了IDisposable的意义,所以稍稍改变一下,定义一个变量来允许Dispose时关闭流。

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

<code>class</code> <code>StreamWrapper : Stream</code>

<code>    </code><code>private</code> <code>readonly</code> <code>Stream stream;</code>

<code>    </code><code>public</code> <code>Stream Stream { </code><code>get</code> <code>{ </code><code>return</code> <code>stream; } }</code>

<code>    </code><code>public</code> <code>StreamWrapper(Stream stream)</code>

<code>        </code><code>this</code><code>.stream = stream;</code>

<code>        </code><code>IsLeavingOpen = </code><code>true</code><code>;</code>

<code>    </code><code>public</code> <code>override</code> <code>void</code> <code>Flush()</code>

<code>        </code><code>stream.Flush();</code>

<code>    </code><code>public</code> <code>override</code> <code>long</code> <code>Seek(</code><code>long</code> <code>offset, SeekOrigin origin)</code>

<code>        </code><code>return</code> <code>stream.Seek(offset, origin);</code>

<code>    </code><code>public</code> <code>override</code> <code>void</code> <code>SetLength(</code><code>long</code> <code>value)</code>

<code>        </code><code>stream.SetLength(value);</code>

<code>    </code><code>public</code> <code>override</code> <code>int</code> <code>Read(</code><code>byte</code><code>[] buffer, </code><code>int</code> <code>offset, </code><code>int</code> <code>count)</code>

<code>        </code><code>return</code> <code>stream.Read(buffer, offset, count);</code>

<code>    </code><code>public</code> <code>override</code> <code>void</code> <code>Write(</code><code>byte</code><code>[] buffer, </code><code>int</code> <code>offset, </code><code>int</code> <code>count)</code>

<code>        </code><code>stream.Write(buffer, offset, count);</code>

<code>    </code><code>public</code> <code>override</code> <code>bool</code> <code>CanRead { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.CanRead; } }</code>

<code>    </code><code>public</code> <code>override</code> <code>bool</code> <code>CanSeek { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.CanSeek; } }</code>

<code>    </code><code>public</code> <code>override</code> <code>bool</code> <code>CanWrite { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.CanWrite; } }</code>

<code>    </code><code>public</code> <code>override</code> <code>long</code> <code>Length { </code><code>get</code> <code>{ </code><code>return</code> <code>stream.Length; } }</code>

<code>    </code><code>public</code> <code>override</code> <code>long</code> <code>Position</code>

<code>        </code><code>get</code> <code>{ </code><code>return</code> <code>stream.Position; }</code>

<code>        </code><code>set</code> <code>{ stream.Position = value; }</code>

<code>    </code><code>public</code> <code>override</code> <code>void</code> <code>Close()</code>

<code>        </code><code>if</code> <code>(IsLeavingOpen)</code>

<code>            </code><code>return</code><code>;</code>

<code>        </code><code>stream.Close();</code>

<code>        </code><code>base</code><code>.Close();</code>

<code>    </code><code>protected</code> <code>override</code> <code>void</code> <code>Dispose(</code><code>bool</code> <code>disposing)</code>

<code>        </code><code>if</code> <code>(disposing)</code>

<code>            </code><code>stream.Dispose();</code>

<code>        </code><code>base</code><code>.Dispose(disposing);</code>

<code>    </code><code>public</code> <code>bool</code> <code>IsLeavingOpen { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

  如果稍稍改变一下写入数据的接口,使用起来也非常方便

<code>    </code><code>using (Stream fileStream = OpenFileStream())</code>

<code>    </code><code>using (StreamWrapper stream = </code><code>new</code> <code>StreamWrapper(fileStream))</code>

<code>        </code><code>WriteHeader(stream);</code>

<code>        </code><code>WriteData(stream);</code>

<code>        </code><code>stream.IsLeavingOpen = </code><code>false</code><code>;</code>

<code>private</code> <code>void</code> <code>WriteHeader(Stream stream)</code>

<code>    </code><code>using (BinaryWriter writer = </code><code>new</code> <code>BinaryWriter(stream))</code>

<code>        </code><code>// ......</code>

<code>private</code> <code>void</code> <code>WriteData(Stream stream)</code>

<code>    </code><code>using (CryptoStream cryptoStream = </code><code>new</code> <code>CryptoStream(stream,</code>

<code>        </code><code>GetCryptoTransform(), CryptoStreamMode.Write))</code>

<code>    </code><code>using (BinaryWriter writer = </code><code>new</code> <code>BinaryWriter(cryptoStream))</code>

<code></code>

本文转自边城__ 51CTO博客,原文链接:http://blog.51cto.com/jamesfancy/1359515,如需转载请自行联系原作者

继续阅读