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

本文介绍了一种在C#中实现文件加密写入的方法,包括如何处理文件流的关闭问题,通过自定义StreamWrapper类解决多次Dispose带来的异常。

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  void  Write()
{
     using  (Stream stream = OpenFileStream())
     using  (BinaryWriter writer =  new  BinaryWriter(stream))
     {
         WriteHeader(writer);
         WriteData(writer);
     }
}
private  void  WriteHeader(BinaryWriter writer)
{
     // ......
}
private  void  WriteData(BinaryWriter writer)
{
     // ......
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  void  Write()
{
     using  (Stream stream = OpenFileStream())
     {
         using  (BinaryWriter headerWriter =  new  BinaryWriter(stream))
         {
             WriteHeader(headerWriter);
         }
         using  (CryptoStream cryptoStream
             new  CryptoStream(stream, GetCryptoTransform(),
                 CryptoStreamMode.Write))
         using  (BinaryWriter dataWriter =  new  BinaryWriter(cryptoStream))
         {
             WriteData(dataWriter);
         }
     }
}


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


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

1
2
3
4
5
public  BinaryWriter(
     Stream output,
     Encoding encoding,
     bool  leaveOpen
)

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


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

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
public  void  Write()
{
     using  (Stream s = OpenFileStream())
     using  (BinaryWriter headWriter =  new  BinaryWriter(s))
     using  (CryptoStream cs =  new  CryptoStream(s, GetCryptoTransform(),
         CryptoStreamMode.Write))
     using  (BinaryWriter dataWriter =  new  BinaryWriter(cs))
     {
         WriterHeader(headWriter);
         WriteData(dataWriter);
     }
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public  void  Write()
{
     Stream stream =  null ;
     BinaryWriter headWriter =  null ;
     CryptoStream cs =  null ;
     BinaryWriter dataWriter =  null ;
     try
     {
         stream = OpenFileStream();
         headWriter =  new  BinaryWriter(stream);
         WriterHeader(headWriter);
         cs =  new  CryptoStream(stream, GetCryptoTransform(), CryptoStreamMode.Write);
         dataWriter =  new  BinaryWriter(cs);
         WriteData(dataWriter);
     }
     finally
     {
         if  (dataWriter !=  null ) { dataWriter.Dispose(); }
         if  (cs !=  null ) { cs.Dispose(); }
         if  (headWriter !=  null ) { headWriter.Dispose(); }
         if  (stream !=  null ) { stream.Dispose(); }
     }
}


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
class  StreamWrapper : Stream
{
     private  readonly  Stream stream;
     public  Stream Stream {  get  return  stream; } }
     public  StreamWrapper(Stream stream)
     {
         this .stream = stream;
         IsLeavingOpen =  true ;
     }
     public  override  void  Flush()
     {
         stream.Flush();
     }
     public  override  long  Seek( long  offset, SeekOrigin origin)
     {
         return  stream.Seek(offset, origin);
     }
     public  override  void  SetLength( long  value)
     {
         stream.SetLength(value);
     }
     public  override  int  Read( byte [] buffer,  int  offset,  int  count)
     {
         return  stream.Read(buffer, offset, count);
     }
     public  override  void  Write( byte [] buffer,  int  offset,  int  count)
     {
         stream.Write(buffer, offset, count);
     }
     public  override  bool  CanRead {  get  return  stream.CanRead; } }
     public  override  bool  CanSeek {  get  return  stream.CanSeek; } }
     public  override  bool  CanWrite {  get  return  stream.CanWrite; } }
     public  override  long  Length {  get  return  stream.Length; } }
     public  override  long  Position
     {
         get  return  stream.Position; }
         set  { stream.Position = value; }
     }
     public  override  void  Close()
     {
         if  (IsLeavingOpen)
         {
             return ;
         }
         stream.Close();
         base .Close();
     }
     protected  override  void  Dispose( bool  disposing)
     {
         if  (IsLeavingOpen)
         {
             return ;
         }
         if  (disposing)
         {
             stream.Dispose();
         }
         base .Dispose(disposing);
     }
     public  bool  IsLeavingOpen {  get set ; }
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public  void  Write()
{
     using (Stream fileStream = OpenFileStream())
     using (StreamWrapper stream =  new  StreamWrapper(fileStream))
     {
         WriteHeader(stream);
         WriteData(stream);
         stream.IsLeavingOpen =  false ;
     }
}
private  void  WriteHeader(Stream stream)
{
     using (BinaryWriter writer =  new  BinaryWriter(stream))
     {
         // ......
     }
}
private  void  WriteData(Stream stream)
{
     using (CryptoStream cryptoStream =  new  CryptoStream(stream,
         GetCryptoTransform(), CryptoStreamMode.Write))
     using (BinaryWriter writer =  new  BinaryWriter(cryptoStream))
     {
         // ......
     }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值