代码改变世界

CSDN第一期总结之四:Stream的问题

2007-09-17 14:23  Jacky_Xu  阅读(376)  评论(0编辑  收藏  举报

对于Stream相关的问题,大致分如下几类。问题一,基本操作的问题;问题二,编码的问题;问题三,尾部处理问题;问题四,Stream缓存问题;问题五,资源释放问题;最后一个问题,说说如何使用Stream来更新大文件部分数据。

IO操作基本上需要用到Stream相关的子类,因此这类问题在CSDN问得也是比较多。其实对于Stream来说,操作起来比较简单,只要对细节的处理稍微注意一下,相信在使用它的时候也会得心应手。

 

对于Stream相关的问题,大致分如下几类。

问题一,基本操作的问题;

问题二,编码的问题;

问题三,尾部处理问题;

问题四,Stream缓存问题;

问题五,资源释放问题;

最后一个问题,说说如何使用Stream来更新大文件部分数据。

 

对于问题一,基本操作的问题,主要是读写问题,主要是出现在文件数据比较大,需要循环写或者读的时候。此时正确读的形式如下。

    // Open a file to read

    using( FileStream fs = new FileStream( yourFile,

                FileMode.Open, FileAccess.Read,

                FileShare.None ) )

    {

        int nRealRead = 0;

        byte[] bBuffer = new byte[1024];

        do

        {

            // Read data

            nRealRead = fs.Read( bBuffer, 0, 1024 );

 

            // Output data

            Debug.WriteLine( Encoding.Default.GetString( bBuffer, 0, nRealRead ) );

        }while( nRealRead == 1024 );

    }

 

可是大多数人第一次完成这样操作的时候,都会在“nRealRead = fs.Read( bBuffer, 0, 1024 );”这一句犯错误。认为第二个参数的偏移量对于Stream而设的,所以认为应该用累加的值,也就是目前总共读了多少的字节数。这里需要理解一下Stream的操作,当进行读或者写操作的时候,Stream的游标会根据所读或者所写得字节而自动向前跟进;其次Stream.Read或者Stream.Write这两个方法中第二个参数是针对第一个Buffer参数而言的,而不是对于Stream的,因此不要在这个地方犯错误。

 

基本问题还牵扯的就是文件打开的方式。有人经常问,如何同时用两个Stream打开同一个文件。其实默认的Stream打开方式是独享的,因此当不指明文件为访问共享的时候,后打开文件操作就会出现异常,因此需要向我上面所写的那样。还有,如果需要指定当前Stream的起始位置,可以通过Seek方法或者设置Position属性来完成。

 

对于问题二,编码问题。有人使用Stream的子类,例如StreamReader之类来打开一个文本文件,发现读出来的数据是乱码,造成这个原因大多数由于文件中含有中文字符,同时打开文件的时候没有指明编码方式。由于英文和中文的编码方式不同,因此在不指明编码的时候有时会造成读取中文错误。此时只要使用StreamReader类型中含有Encoding参数的构造函数即可,例如:

using( StreamReader sr = new StreamReader( yourFile, Encoding.Default ) )

这里只是采用系统默认的编码方式,但有可能不太适合你文件的编码方式,因此需要在实际应用去调试和变换这个参数。

 

问题三是,Stream尾部处理问题。此类问题所展现的现象如,复制文件的时候文件会增大。因此在使用Stream.ReadStream.Write的时候,要通过方法的返回值,来标明真正读和写的字节数,就像前面所写的那样。

    // Read data

    nRealRead = fs.Read( bBuffer, 0, 1024 );

 

    // Output data

    Debug.WriteLine( Encoding.Default.GetString( bBuffer, 0, nRealRead ) );

此时在输出的时候用的不是“1024”,而是“nRealRead”做为字节有效标示。

 

对于问题四,Stream缓存的问题,这主要表现在写的时候。为了避免频繁操作IO而降低效率,大多数Stream采用异步写的方式,也就是Stream对象要配备有一定的缓存,来暂时保存写的数据。但缓存是有限的,当缓存已满后会造成后续写的数据不能写入,从而导致数据丢失。那么此时需要显示的调用Stream.Flush方法,来把缓存的数据写入到文件中并清空缓存。其实这并不是唯一方法,在一些Stream的子类中还提供了设置BufferSize的方法,或者提供了设置AutoFlush属性来实现自动写入等等,因此这里大家可以根据不同需要而选择不同方法来完成。

 

对于Stream的释放问题,这可能不单单是使用Stream的问题,可能是使用C#编程而造成的不良习惯。虽说C#的资源是受托管的,但是对于Stream来说,如果不及时释放,那么当其他线程或者进程使用此文件的时候就会造成无法打开的现象(由于Stream大多数都是以独享方式打开),而且没有及时关闭,所占用的Buffer无法及时释放。对于资源释放问题,我为此专门写过一篇文章,如果有兴趣的话不妨看看。

http://blog.csdn.net/Knight94/archive/2006/08/05/1023352.aspx

因此养成一个好的习惯至关重要。其实释放Stream很简单,要么显示的调用其的CloseDispose这两个方法,要么使用using程序块,就像我前面所写的那样。

 

最后一个就是如何使用Stream来更新大文件。比较常见的就是,当文件比较大,但是需要修改的部分很少,因此想要通过Stream直接在某个位置进行类似于删除、插入或者替换等操作。

 

对于一个文件的更新操作,大致分为三种,这里主要是考虑更新的位置和更新数据长度。

第一种对于文件尾扩展的操作,内容长度不限;

第二种等字节的替换操作,位置不限;

最后一种就是位置不固定,字节数不确定。

上面所说的前两种,进行处理比较简单。对于第一种,只要设置FileMode的时候增加Append标示即可。而对于等字节的替换,就更简单了,直接通过Stream.Seek找到指定的位置,然后调用Stream.Write即可。

而最后一个,是最麻烦的。比较简单的解决方式,创建一个临时文件,然后一边读一边写,遇到需要修改的,先读出来再修改最后再写入。等全部写完了,删除旧文件,修改临时文件的名称为原来名字。

比较麻烦的解决方式,就是通过Share方式,用一个读Stream和一个写Stream直接操纵源文件。这里需要注意的是,为了保证新写的数据不要冲掉还没读出来的数据,也就是说要控制写Stream所写的位置不要超过要读的位置。举例说,目前需要读的位置是文件的800字节处,也就是说800字节以后还没读出来处理,此时写Stream在写完数据后,Stream的位置不能超过800字节,如果写采用的是缓存,那么超过800位置的数据不要立刻通过Flush进行提交。总的来说,通过两个Stream来操作同一个文件,对于这一点要特别注意,处理不好要造成死循环。

 

Stream的问题相对比较简单,大多数人操作的时候不注意细节。所以我这里只是稍加说明,不做特别细的说明。