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.Read和Stream.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很簡單,要么顯示的調用其的Close和Dispose這兩個方法,要么使用using程序塊,就像我前面所寫的那樣。
最后一個就是如何使用Stream來更新大文件。比較常見的就是,當文件比較大,但是需要修改的部分很少,因此想要通過Stream直接在某個位置進行類似于刪除、插入或者替換等操作。
對于一個文件的更新操作,大致分為三種,這里主要是考慮更新的位置和更新數據長度。
第一種對于文件尾擴展的操作,內容長度不限;
第二種等字節的替換操作,位置不限;
最后一種就是位置不固定,字節數不確定。
上面所說的前兩種,進行處理比較簡單。對于第一種,只要設置FileMode的時候增加Append標示即可。而對于等字節的替換,就更簡單了,直接通過Stream.Seek找到指定的位置,然后調用Stream.Write即可。
而最后一個,是最麻煩的。比較簡單的解決方式,創建一個臨時文件,然后一邊讀一邊寫,遇到需要修改的,先讀出來再修改最后再寫入。等全部寫完了,刪除舊文件,修改臨時文件的名稱為原來名字。
比較麻煩的解決方式,就是通過Share方式,用一個讀Stream和一個寫Stream直接操縱源文件。這里需要注意的是,為了保證新寫的數據不要沖掉還沒讀出來的數據,也就是說要控制寫Stream所寫的位置不要超過要讀的位置。舉例說,目前需要讀的位置是文件的800字節處,也就是說800字節以后還沒讀出來處理,此時寫Stream在寫完數據后,Stream的位置不能超過800字節,如果寫采用的是緩存,那么超過800位置的數據不要立刻通過Flush進行提交。總的來說,通過兩個Stream來操作同一個文件,對于這一點要特別注意,處理不好要造成死循環。
Stream的問題相對比較簡單,大多數人操作的時候不注意細節。所以我這里只是稍加說明,不做特別細的說明。