C# FileStream文件讀寫詳解
FileStream對象表示在磁盤或網絡路徑上指向文件的流。這個類提供了在文件中讀寫字節(jié)的方法,但經常使用StreamReader或StreamWriter執(zhí)行這些功能。這是因為FileStream類操作的是字節(jié)和字節(jié)數組,而Stream類操作的是字符數據。字符數據易于使用,但是有些操作,比如隨機文件訪問(訪問文件中間某點的數據),就必須由FileStream對象執(zhí)行,稍后對此進行介紹。
還有幾種方法可以創(chuàng)建FileStream對象。構造函數具有許多不同的重載版本,最簡單的構造函數僅僅帶有兩個參數,即文件名和FileMode枚舉值。
FileStream aFile = new FileStream(filename, FileMode.Member);
FileMode枚舉有幾個成員,規(guī)定了如何打開或創(chuàng)建文件。稍后介紹這些枚舉成員。另一個常用的構造函數如下:
FileStream aFile = new FileStream(filename, FileMode.Member, FileAccess. Member);
第三個參數是FileAccess枚舉的一個成員,它指定了流的作用。FileAccess枚舉的成員如表22-6所示。
表 22-6
| 成員 | 說明 |
|---|---|
| Read | 打開文件,用于只讀 |
| Write | 打開文件,用于只寫 |
| ReadWrite | 打開文件,用于讀寫 |
對文件進行不是FileAccess枚舉成員指定的操作會導致拋出異常。此屬性的作用是,基于用戶的身份驗證級別改變用戶對文件的訪問權限。
在FileStream構造函數不使用FileAccess枚舉參數的版本中,使用默認值FileAccess. ReadWrite。
FileMode枚舉成員如表22-7所示。使用每個值會發(fā)生什么,取決于指定的文件名是否表示已有的文件。注意這個表中的項表示創(chuàng)建流時該流指向文件中的位置,下一節(jié)將詳細討論這個主題。除非特別說明,否則流就指向文件的開頭。
表 22-7
| 成員 | 文件存在 | 文件不存在 |
|---|---|---|
| Append | 打開文件,流指向文件的末尾,只能與枚舉FileAccess.Write聯(lián)合使用 | 創(chuàng)建一個新文件。只能與枚舉FileAccess.Write聯(lián)合使用 |
| Create | 刪除該文件,然后創(chuàng)建新文件 | 創(chuàng)建新文件 |
| CreateNew | 拋出異常 | 創(chuàng)建新文件 |
| Open | 打開現有的文件,流指向文件的開頭 | 拋出異常 |
| OpenOrCreate | 打開文件,流指向文件的開頭 | 創(chuàng)建新文件 |
| Truncate | 打開現有文件,清除其內容。流指向文件的開頭,保留文件的初始創(chuàng)建日期 | 拋出異常 |
File和FileInfo類都提供了OpenRead()和OpenWrite()方法,更易于創(chuàng)建FileStream對象。前者打開了只讀訪問的文件,后者只允許寫入文件。這些都提供了快捷方式,因此不必以FileStream構造函數的參數形式提供前面所有的信息。例如,下面的代碼行打開了用于只讀訪問的Data.txt文件:
FileStream aFile = File.OpenRead("Data.txt");
注意下面的代碼執(zhí)行同樣的功能:
FileInfo aFileInfo = new FileInfo("Data.txt");
FileStream aFile = aFile.OpenRead();
1. 文件位置
FileStream類維護內部文件指針,該指針指向文件中進行下一次讀寫操作的位置。在大多數情況下,當打開文件時,它就指向文件的開始位置,但是此指針可以修改。這允許應用程序在文件的任何位置讀寫,隨機訪問文件,或直接跳到文件的特定位置上。當處理大型文件時,這非常省時,因為馬上可以定位到正確的位置。
實現此功能的方法是Seek()方法,它有兩個參數:第一個參數規(guī)定文件指針以字節(jié)為單位的移動距離。第二個參數規(guī)定開始計算的起始位置,用SeekOrigin枚舉的一個值表示。Seek Origin枚舉包含3個值:Begin、Current和End。
例如,下面的代碼行將文件指針移動到文件的第8個字節(jié),其起始位置就是文件的第1個字節(jié):
aFile.Seek(8,SeekOrigin.Begin);
下面的代碼行將指針從當前位置開始向前移動2個字節(jié)。如果在上面的代碼行之后執(zhí)行下面的代碼,文件指針就指向文件的第10個字節(jié):
aFile.Seek(2,SeekOrigin.Current);
注意讀寫文件時,文件指針也會改變。在讀取了10個字節(jié)之后,文件指針就指向被讀取的第10個字節(jié)之后的字節(jié)。
也可以規(guī)定負查找位置,這可以與SeekOrigin.End枚舉值一起使用,查找靠近文件末端的位置。下面的代碼會查找文件中倒數第5個字節(jié):
aFile.Seek(–5, SeekOrigin.End);
以這種方式訪問的文件有時稱為隨機訪問文件,因為應用程序可以訪問文件中的任何位置。稍后介紹的Stream類可以連續(xù)地訪問文件,不允許以這種方式操作文件指針。
2. 讀取數據
使用FileStream類讀取數據不像使用本章后面介紹的StreamReader類讀取數據那樣容易。這是因為FileStream類只能處理原始字節(jié)(raw byte)。處理原始字節(jié)的功能使FileStream類可以用于任何數據文件,而不僅僅是文本文件。通過讀取字節(jié)數據,FileStream對象可以用于讀取圖像和聲音的文件。這種靈活性的代價是,不能使用FileStream類將數據直接讀入字符串,而使用StreamReader類卻可以這樣處理。但是有幾種轉換類可以很容易地將字節(jié)數組轉換為字符數組,或者進行相反的操作。
FileStream.Read()方法是從FileStream對象所指向的文件中訪問數據的主要手段。這個方法從文件中讀取數據,再把數據寫入一個字節(jié)數組。它有三個參數:第一個參數是傳輸進來的字節(jié)數組,用以接受FileStream對象中的數據。第二個參數是字節(jié)數組中開始寫入數據的位置。它通常是0,表示從數組開端向文件中寫入數據。最后一個參數指定從文件中讀出多少字節(jié)。
下面的示例演示了從隨機訪問文件中讀取數據。要讀取的文件實際是為此示例創(chuàng)建的類文件。
試試看:從隨機訪問文件中讀取數據
(1) 在目錄C:\BegVCSharp\Chapter22下創(chuàng)建一個新的控制臺應用程序ReadFile。
(2) 在Program.cs文件的頂部添加下面的using指令:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
(3) 在Main()方法中添加下面的代碼:
static void Main(string[] args)
{
byte[] byData = new byte[200];
char[] charData = new Char[200];
try
{
FileStream aFile = new FileStream("E:\\Workplace\\TestSolution\\Test.ConsoleApplication\\Program.cs",FileMode.Open);
aFile.Seek(135,SeekOrigin.Begin);
aFile.Read(byData,0,200);
}
catch(IOException e)
{
Console.WriteLine("An IO exception has been thrown!");
Console.WriteLine(e.ToString());
Console.ReadKey();
return;
}
Decoder d = Encoding.UTF8.GetDecoder();
d.GetChars(byData, 0, byData.Length, charData, 0);
Console.WriteLine(charData);
Console.ReadKey();
}
(4) 運行應用程序。結果如圖22-2所示。

圖 22-2
示例的說明
此應用程序打開自己的.cs文件,用于讀取。它在下面的代碼行中使用..字符串向上逐級導航兩個目錄,找到該文件:
FileStream aFile = new FileStream("E:\\Workplace\\TestSolution\\Test.ConsoleApplication\\Program.cs",FileMode.Open);
下面兩行代碼實現查找工作,并從文件的具體位置讀取字節(jié):
aFile.Seek(135,SeekOrigin.Begin);
aFile.Read(byData,0,200);
第一行代碼將文件指針移動到文件的第135個字節(jié)。在Program.cs中,這是namespace的 “n”;其前面的135個字符是using指令和相關的#region。第二行將接下來的200個字節(jié)讀入到byData字節(jié)數組中。
注意這兩行代碼封裝在try…catch塊中,以處理可能拋出的異常。
try
{
aFile.Seek(135,SeekOrigin.Begin);
aFile.Read(byData,0,100);
}
catch(IOException e)
{
Console.WriteLine("An IO exception has been thrown!");
Console.WriteLine(e.ToString());
Console.ReadKey();
return;
}
文件IO涉及到的所有操作都可以拋出類型為IOException的異常。所有產品代碼都必須包含錯誤處理,尤其是處理文件系統(tǒng)時更是如此。本章的所有示例都具有錯誤處理的基本形式。
從文件中獲取了字節(jié)數組后,就需要將其轉換為字符數組,以便在控制臺顯示它。為此,使用System.Text命名空間的Decoder類。此類用于將原始字節(jié)轉換為更有用的項,比如字符:
Decoder d = Encoding.UTF8.GetDecoder();
d.GetChars(byData, 0, byData.Length, charData, 0);
這些代碼基于UTF8編碼模式創(chuàng)建了Decoder對象。這就是Unicode編碼模式。然后調用GetChars()方法,此方法提取字節(jié)數組,將它轉換為字符數組。完成之后,就可以將字符數組輸出到控制臺。
3. 寫入數據
向隨機訪問文件中寫入數據的過程與從中讀取數據非常類似。首先需要創(chuàng)建一個字節(jié)數組;最簡單的辦法是首先構建要寫入文件的字符數組。然后使用Encoder對象將其轉換為字節(jié)數組,其用法非常類似于Decoder。最后調用Write()方法,將字節(jié)數組傳送到文件中。
下面構建一個簡單的示例演示其過程。
試試看:將數據寫入隨機訪問文件
(1) 在C:\BegVCSharp\Chapter22目錄下創(chuàng)建一個新的控制臺應用程序WriteFile。
(2) 如上所示,在Program.cs文件頂部添加下面的using指令:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
(3) 在Main()方法中添加下面的代碼:
static void Main(string[] args)
{
byte[] byData;
char[] charData;
try
{
FileStream aFile = new FileStream("Temp.txt", FileMode.Create);
charData = "My pink half of the drainpipe.".ToCharArray();
byData = new byte[charData.Length];
Encoder e = Encoding.UTF8.GetEncoder();
e.GetBytes(charData, 0, charData.Length, byData, 0, true);
// Move file pointer to beginning of file.
aFile.Seek(0, SeekOrigin.Begin);
aFile.Write(byData, 0, byData.Length);
}
catch (IOException ex)
{
Console.WriteLine("An IO exception has been thrown!");
Console.WriteLine(ex.ToString());
Console.ReadKey();
return;
}
}
(4) 運行該應用程序。稍后就將其關閉。
(5) 導航到應用程序目錄 —— 在目錄中已經保存了文件,因為我們使用了相對路徑。目錄位于WriteFile\bin\Debug文件夾。打開Temp.txt文件??梢栽谖募锌吹饺鐖D22-3所示的文本。

圖 22-3
示例的說明
此應用程序在自己的目錄中打開文件,并在文件中寫入了一個簡單的字符串。在結構上這個示例非常類似于前面的示例,只是用Write()代替了Read(),用Encoder代替了Decoder。
下面的代碼行使用String類的ToCharArray()靜態(tài)方法,創(chuàng)建了字符數組。因為C#中的所有事物都是對象,文本“My pink half of the drainpipe.”實際上是一個String對象,所以甚至可以在字符串上調用這些靜態(tài)方法。
CharData = " My pink half of the drainpipe. ".ToCharArray();
下面的代碼行顯示了如何將字符數組轉換為FileStream對象需要的正確字節(jié)數組。
Encoder e = Endoding.UTF8.GetEncoder();
e.GetBytes(charData,0,charData.Length, byData,0,true);
這次,要基于UTF8編碼方法來創(chuàng)建Encoder對象。也可以將Unicode用于解碼。這里在寫入流之前,需要將字符數據編碼為正確的字節(jié)格式。在GetBytes()方法中可以完成這些工作,它可以將字符數組轉換為字節(jié)數組,并將字符數組作為第一個參數(本例中的charData),將該數組中起始位置的下標作為第二個參數(0表示數組的開頭)。第三個參數是要轉換的字符數量(charData.Length,charData數組中的元素個數)。第四個參數是在其中置入數據的字節(jié)數組(byData),第五個參數是在字節(jié)數組中開始寫入位置的下標(0表示byData數組的開頭)。
最后一個參數決定在結束后Encoder對象是否應該更新其狀態(tài),即Encoder對象是否仍然保留它原來在字節(jié)數組中的內存位置。這有助于以后調用Encoder對象,但是當只進行單一調用時,這就沒有什么意義。最后對Encoder的調用必須將此參數設置為true,以清空其內存,釋放對象,用于垃圾回收。
之后,使用Write()方法向FileStream寫入字節(jié)數組就非常簡單:
aFile.Seek(0,SeekOrigin.Begin);
aFile.Write(byData,0,byData.Length);
與Read()方法一樣,Write()方法也有三個參數:要寫入的數組,開始寫入的數組下標和要寫入的字節(jié)數。
相關文章
C#中Hashtable和Dictionary的區(qū)別與用法示例
由于 Hashtable 和 Dictionary 同時存在, 在使用場景上必然存在選擇性, 并不任何時刻都能相互替代。所以這篇文章主要給大家介紹了關于C#中Hashtable和Dictionary區(qū)別的相關資料,需要的朋友可以參考下2021-05-05
C#靜態(tài)代碼織入AOP組件之Rougamo的使用詳解
Rougamo是一個靜態(tài)代碼織入的AOP組件,同為AOP組件較為常用的有Castle、Autofac、AspectCore等,下面就跟隨小編一起來學習一下它的具體使用吧2024-01-01
c# 從內存中釋放Selenium chromedriver.exe
這篇文章主要介紹了c# 從內存中釋放Selenium chromedriver.exe的方法,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2021-01-01

