FileStream對象表示在磁盤或網絡路徑上指向檔案的流。這個類提供了在檔案中讀寫位元組的方法,但經常使用StreamReader或StreamWriter執行這些功能。這是因為FileStream類操作的是位元組和位元組數組,而Stream類操作的是字元資料。字元資料易于使用,但是有些操作,比如随機檔案通路(通路檔案中間某點的資料),就必須由FileStream對象執行,稍後對此進行介紹。
還有幾種方法可以建立FileStream對象。構造函數具有許多不同的重載版本,最簡單的構造函數僅僅帶有兩個參數,即檔案名和FileMode枚舉值。
|
FileMode枚舉有幾個成員,規定了如何打開或建立檔案。稍後介紹這些枚舉成員。另一個常用的構造函數如下:
|
第三個參數是FileAccess枚舉的一個成員,它指定了流的作用。FileAccess枚舉的成員如表22-6所示。
表 22-6
成 員 | 說 明 |
Read | 打開檔案,用于隻讀 |
Write | 打開檔案,用于隻寫 |
ReadWrite | 打開檔案,用于讀寫 |
對檔案進行不是FileAccess枚舉成員指定的操作會導緻抛出異常。此屬性的作用是,基于使用者的身份驗證級别改變使用者對檔案的通路權限。
在FileStream構造函數不使用FileAccess枚舉參數的版本中,使用預設值FileAccess. ReadWrite。
FileMode枚舉成員如表22-7所示。使用每個值會發生什麼,取決于指定的檔案名是否表示已有的檔案。注意這個表中的項表示建立流時該流指向檔案中的位置,下一節将詳細讨論這個主題。除非特别說明,否則流就指向檔案的開頭。
表 22-7
成 員 | 文 件 存 在 | 檔案不存在 |
Append | 打開檔案,流指向檔案的末尾,隻能與枚舉FileAccess.Write聯合使用 | 建立一個新檔案。隻能與枚舉FileAccess.Write聯合使用 |
Create | 删除該檔案,然後建立新檔案 | 建立新檔案 |
CreateNew | 抛出異常 | 建立新檔案 |
Open | 打開現有的檔案,流指向檔案的開頭 | 抛出異常 |
OpenOrCreate | 打開檔案,流指向檔案的開頭 | 建立新檔案 |
Truncate | 打開現有檔案,清除其内容。流指向檔案的開頭,保留檔案的初始建立日期 | 抛出異常 |
File和FileInfo類都提供了OpenRead()和OpenWrite()方法,更易于建立FileStream對象。前者打開了隻讀通路的檔案,後者隻允許寫入檔案。這些都提供了快捷方式,是以不必以FileStream構造函數的參數形式提供前面所有的資訊。例如,下面的代碼行打開了用于隻讀通路的Data.txt檔案:
|
注意下面的代碼執行同樣的功能:
|
1. 檔案位置
FileStream類維護内部檔案指針,該指針指向檔案中進行下一次讀寫操作的位置。在大多數情況下,當打開檔案時,它就指向檔案的開始位置,但是此指針可以修改。這允許應用程式在檔案的任何位置讀寫,随機通路檔案,或直接跳到檔案的特定位置上。當處理大型檔案時,這非常省時,因為馬上可以定位到正确的位置。
實作此功能的方法是Seek()方法,它有兩個參數:第一個參數規定檔案指針以位元組為機關的移動距離。第二個參數規定開始計算的起始位置,用SeekOrigin枚舉的一個值表示。Seek Origin枚舉包含3個值:Begin、Current和End。
例如,下面的代碼行将檔案指針移動到檔案的第8個位元組,其起始位置就是檔案的第1個位元組:
|
下面的代碼行将指針從目前位置開始向前移動2個位元組。如果在上面的代碼行之後執行下面的代碼,檔案指針就指向檔案的第10個位元組:
|
注意讀寫檔案時,檔案指針也會改變。在讀取了10個位元組之後,檔案指針就指向被讀取的第10個位元組之後的位元組。
也可以規定負查找位置,這可以與SeekOrigin.End枚舉值一起使用,查找靠近檔案末端的位置。下面的代碼會查找檔案中倒數第5個位元組:
|
以這種方式通路的檔案有時稱為随機通路檔案,因為應用程式可以通路檔案中的任何位置。稍後介紹的Stream類可以連續地通路檔案,不允許以這種方式操作檔案指針。
2. 讀取資料
使用FileStream類讀取資料不像使用本章後面介紹的StreamReader類讀取資料那樣容易。這是因為FileStream類隻能處理原始位元組(raw byte)。處理原始位元組的功能使FileStream類可以用于任何資料檔案,而不僅僅是文本檔案。通過讀取位元組資料,FileStream對象可以用于讀取圖像和聲音的檔案。這種靈活性的代價是,不能使用FileStream類将資料直接讀入字元串,而使用StreamReader類卻可以這樣處理。但是有幾種轉換類可以很容易地将位元組數組轉換為字元數組,或者進行相反的操作。
FileStream.Read()方法是從FileStream對象所指向的檔案中通路資料的主要手段。這個方法從檔案中讀取資料,再把資料寫入一個位元組數組。它有三個參數:第一個參數是傳輸進來的位元組數組,用以接受FileStream對象中的資料。第二個參數是位元組數組中開始寫入資料的位置。它通常是0,表示從數組開端向檔案中寫入資料。最後一個參數指定從檔案中讀出多少位元組。
下面的示例示範了從随機通路檔案中讀取資料。要讀取的檔案實際是為此示例建立的類檔案。
試試看:從随機通路檔案中讀取資料
(1) 在目錄C:/BegVCSharp/Chapter22下建立一個新的控制台應用程式ReadFile。
(2) 在Program.cs檔案的頂部添加下面的using指令:
|
(3) 在Main()方法中添加下面的代碼:
|
(4) 運作應用程式。結果如圖22-2所示。
![]() |
圖 22-2 |
示例的說明
此應用程式打開自己的.cs檔案,用于讀取。它在下面的代碼行中使用..字元串向上逐級導航兩個目錄,找到該檔案:
|
下面兩行代碼實作查找工作,并從檔案的具體位置讀取位元組:
|
第一行代碼将檔案指針移動到檔案的第135個位元組。在Program.cs中,這是namespace的 “n”;其前面的135個字元是using指令和相關的#region。第二行将接下來的200個位元組讀入到byData位元組數組中。
注意這兩行代碼封裝在try…catch塊中,以處理可能抛出的異常。
|
檔案IO涉及到的所有操作都可以抛出類型為IOException的異常。所有産品代碼都必須包含錯誤處理,尤其是處理檔案系統時更是如此。本章的所有示例都具有錯誤處理的基本形式。
從檔案中擷取了位元組數組後,就需要将其轉換為字元數組,以便在控制台顯示它。為此,使用System.Text命名空間的Decoder類。此類用于将原始位元組轉換為更有用的項,比如字元:
|
這些代碼基于UTF8編碼模式建立了Decoder對象。這就是Unicode編碼模式。然後調用GetChars()方法,此方法提取位元組數組,将它轉換為字元數組。完成之後,就可以将字元數組輸出到控制台。
3. 寫入資料
向随機通路檔案中寫入資料的過程與從中讀取資料非常類似。首先需要建立一個位元組數組;最簡單的辦法是首先建構要寫入檔案的字元數組。然後使用Encoder對象将其轉換為位元組數組,其用法非常類似于Decoder。最後調用Write()方法,将位元組數組傳送到檔案中。
下面建構一個簡單的示例示範其過程。
試試看:将資料寫入随機通路檔案
(1) 在C:/BegVCSharp/Chapter22目錄下建立一個新的控制台應用程式WriteFile。
(2) 如上所示,在Program.cs檔案頂部添加下面的using指令:
|
(3) 在Main()方法中添加下面的代碼:
|
(4) 運作該應用程式。稍後就将其關閉。
(5) 導航到應用程式目錄 —— 在目錄中已經儲存了檔案,因為我們使用了相對路徑。目錄位于WriteFile/bin/Debug檔案夾。打開Temp.txt檔案。可以在檔案中看到如圖22-3所示的文本。
|
圖 22-3 |
示例的說明
此應用程式在自己的目錄中打開檔案,并在檔案中寫入了一個簡單的字元串。在結構上這個示例非常類似于前面的示例,隻是用Write()代替了Read(),用Encoder代替了Decoder。
下面的代碼行使用String類的ToCharArray()靜态方法,建立了字元數組。因為C#中的所有事物都是對象,文本“My pink half of the drainpipe.”實際上是一個String對象,是以甚至可以在字元串上調用這些靜态方法。
|
下面的代碼行顯示了如何将字元數組轉換為FileStream對象需要的正确位元組數組。
|
這次,要基于UTF8編碼方法來建立Encoder對象。也可以将Unicode用于解碼。這裡在寫入流之前,需要将字元資料編碼為正确的位元組格式。在GetBytes()方法中可以完成這些工作,它可以将字元數組轉換為位元組數組,并将字元數組作為第一個參數(本例中的charData),将該數組中起始位置的下标作為第二個參數(0表示數組的開頭)。第三個參數是要轉換的字元數量(charData.Length,charData數組中的元素個數)。第四個參數是在其中置入資料的位元組數組(byData),第五個參數是在位元組數組中開始寫入位置的下标(0表示byData數組的開頭)。
最後一個參數決定在結束後Encoder對象是否應該更新其狀态,即Encoder對象是否仍然保留它原來在位元組數組中的記憶體位置。這有助于以後調用Encoder對象,但是當隻進行單一調用時,這就沒有什麼意義。最後對Encoder的調用必須将此參數設定為true,以清空其記憶體,釋放對象,用于垃圾回收。
之後,使用Write()方法向FileStream寫入位元組數組就非常簡單:
|
與Read()方法一樣,Write()方法也有三個參數:要寫入的數組,開始寫入的數組下标和要寫入的位元組數