C#文件和流
本文主要是對C#中的流進行詳細講解,關於C#中的文件操作,考慮到后期.net core跨平台,相關操作可能會發生很大變化,所以此處不對文件系統(包括目錄、文件)過多的講解,只會描述出在.net framework下常用的類,具體用法請參見官方API文檔。
管理文件系統
在Windows上,用於瀏覽文件系統和執行操作的相關類有:
FileSystemInfo
:這是表示任何文件系統對象的基類。FileInfo
和File
:這些類表示文件系統上的文件。DirectoryInfo
和Directory
:這些類表示文件系統上的目錄。Path
:這個類包含的靜態成員可以用於處理路徑名。DriveInfo
:這個類的屬性和方法提供了指定驅動器的信息。
Directory
類和File
類只包含靜態方法,不能被實例化。如果只對文件夾或文件執行一個操作,使用這個類就很有效,因為這樣可以省去創建.NET對象的系統開銷。
DirectoryInfo
類和FileInfo
類的成員都不是靜態的,使用時需要被實例化。如果使用同一個對象執行多個操作,使用這些類就比較有效。【這是因為在構造它們將讀取合適文件系統對象的身份驗證和其他信息,無論對每個對象(類實例)調用多少方法,都不需要再次讀取這些信息】
檢查驅動器信息
有時在處理文件和目錄之前,需要檢查驅動器信息,可以使用DriveInfo
類實現。
DriveInfo
類可以掃描系統,提供可用驅動器的列表,還可以進一步提供任何驅動器的大量細節信息。
關於DriveInfo
的用法,請參考官方API說明:https://docs.microsoft.com/zh-cn/dotnet/api/system.io.driveinfo
使用Path類處理文件和目錄的路徑
如果只是單純的使用字符串連接操作符合並多個文件夾和文件時,很容易遺漏單個分隔符或使用太多的字符。可以使用Path
類代替字符串拼接路徑,Path
類會添加缺少的分隔符,而且可以基於Windows和Unix系統,處理不同的平台需求。
Path
類可以使用以下幾個屬性處理Windows和Unix平台的路徑特殊符號:
Path.VolumeSeparatorChar
:提供特定於平台的卷分隔符。 此字段的值在Windows和Macintosh上為冒號(:
),在UNIX操作系統上為斜杠(/
)。這對於解析諸如“c:\ windows
”或“MacVolume:System Folder
”之類的路徑非常有用。Path.DirectorySeparatorChar
:提供特定於平台的字符,用於分隔反映分層文件系統組織的路徑字符串中的目錄級別 ,該屬性的值為左斜杠(\
)。Path.AltDirectorySeparatorChar
:提供特定於平台的備用字符,用於分隔反映分層文件系統組織的路徑字符串中的目錄級別 。此字段可以設置為與DirectorySeparatorChar
相同的值。AltDirectorySeparatorChar
和DirectorySeparatorChar
都可用於分隔路徑字符串中的目錄級別。 Windows、UNIX和Macintosh上該字段的值是斜杠(/
)。Path.PathSeparator
:特定於平台的分隔符,用於分隔環境變量中的路徑字符串。 在基於Windows的桌面平台上,默認情況下,此字段的值為分號(;
),但在其他平台上可能會有所不同。
除此之外,Path
類還有如下幾個非常適用的方法:
Path.GetInvalidPathChars()
:獲取包含不允許在路徑名中使用的字符的數組。Path.GetInvalidFileNameChars()
:獲取包含不允許在文件名中使用的字符的數組。Path.GetTempPath()
:返回當前用戶的臨時文件夾的路徑。Path.GetTempFileName()
:在磁盤上創建磁唯一命名的零字節的臨時文件並返回該文件的完整路徑。Path.GetRandomFileName()
:返回隨機文件夾名或文件名。Path.Combine()
:合並路徑。Path.ChangeExtension()
:更改路徑字符串的擴展名。
使用Environment類處理特殊文件夾
Environment
類定義了一組特殊的文件夾。注意:該類不能用於.net core中。
Environment
的SpecialFolder
屬性是一個巨大的枚舉,通過該屬性,可以得到window系統上的音樂、圖片、程序文件、應用程序數據、以及許多其他文件夾的路徑值。
除此之外,還可以調用Environment.GetEnvironmentVariable()
方法,根據指定的環境變量名得到該變量的具體值。
關於Environment
的具體使用,請參見官方API說明:https://docs.microsoft.com/zh-cn/dotnet/api/system.environment
使用File、FileInfo和Directory、DirectoryInfo類處理文件和文件夾
File
、FileInfo
、Directory
、DirectoryInfo
的詳細操作,可直接參見官方API文檔。這里只對其中需要注意的地方進行敘述。
File
類提供了簡單創建文件並寫入和讀取文本的操作方法,如File.WriteAllText()
、File.ReadAllText()
方法,但是需要注意的是,使用File
在字符串中讀寫文件只適用於小型文本文件。並且,以這種方式讀取、保存完整的文件是有限制的。.NET字符串的限制是2GB,雖然對於許多文本文件而言,這已經足夠了,但是最好不要讓用戶等待將1GB的文件加載到字符串中,這將非常耗時和耗資源,而應該使用流進行讀寫操作。
使用流處理文件
流是一個用於傳輸數據的對象,分為讀取流和寫入流。傳輸數據可以基於文件、內存、網絡或其他任意數據源。.NET中提供了一下幾種用來操作的流:
MemoryStream
:創建其后備存儲為內存的流。 用來讀寫內存。System.Net.Sockets.NetworkStream
:為網絡訪問提供基礎數據流。 用來處理網絡數據。FileStream
:創建用來處理文件的流,支持同步和異步。這個類主要用於在二進制文件中讀寫二進制數據。System.IO.Compression.DeflateStream
:提供使用Deflate
算法壓縮和解壓縮流的方法和屬性。System.Security.Cryptography.CryptoStream
: 定義將數據流鏈接到加密轉換的流。
上述的這些類都派生自基類Stream
,多個流之間可以相互鏈接(轉換),相互寫入。
對於文件的讀寫,最常用的類如下:
FileStream
:文件流,這個類主要用於在二進制文件中讀寫二進制數據。StreamReader
和StreamWriter
:這兩個類專門用於讀寫文本格式的流產品API。可以通過它們的基類看出主要針對的是文本格式的文件。BinaryReader
和BinaryWriter
:這兩個類專門用於讀寫二進制格式的流產品API。
各種流如何選擇
在不使用其他流的情況下,單純的使用FileStream
既可以處理文本格式的文件也可以處理二進制數據文件,它是基於字節來讀取或寫入數據的。對於文本文件,可能需要先分析文本文件的編碼,以便能夠正確讀取和寫入不同編碼格式的文本。
如果內容是文本格式的文件,推薦使用StreamReader
和SreamWriter
進行讀寫操作,不需要考慮編碼格式問題,默認采用UTF-8
編碼(可在構造函數中自定義編碼格式)。
如果內容是二進制格式的文件,推薦使用BinaryReader和BinaryWriter進行讀寫操作,將以二進制格式而不是文本格式寫入文件,並且不需要使用任何編碼。
使用文件流FileStream
可以使用FileStream
的構造函數來創建FileStream
對象,除了這種方式外,還可以直接使用File
類的OpenRead()
方法創建FileStream
,OpenRead()
方法打開一個文件(類似於FileMode.Open
),返回一個可以讀取的流(FileAccess.Read
),也允許其他進程執行讀取訪問(FileShare.Read
)。對應的寫入流可以使用File.OpenWrite()
方法得到。
無論是讀取流還是寫入流,它們都是FileStream
對象,只是對應的FileAccess
代表的操作不同,建議在給流對象命名時,最好有是讀取還是寫入的標識名。
獲取流相關信息
在下面的示例中,分別獲取Stream
類的成員屬性,得到流處理相關信息。
private void ShowStreamInfomation(FileStream stream) { Console.WriteLine("當前流是否可讀取:" + stream.CanRead); Console.WriteLine("當前流是否可寫入:" + stream.CanWrite); Console.WriteLine("當前流是否支持搜索:" + stream.CanSeek); Console.WriteLine("當前流是否可以超時:" + stream.CanTimeout); Console.WriteLine("當前流長度:" + stream.Length); Console.WriteLine("當前流的位置:" + stream.Position); //如果可以超時 if (stream.CanTimeout) { Console.WriteLine("流在嘗試讀取多少毫秒后超時:" + stream.ReadTimeout); Console.WriteLine("流在嘗試寫入多少毫秒后超時:" + stream.WriteTimeout); } }
分析文本文件的編碼
對於文本文件,首先是讀取流中的第一個字節——序言。序言提供了文件如何編碼的信息(使用的文本編碼格式),這也稱為字節順序標記(Byte Order Mark,BOM
)。可以使用如下方法,獲取BOM
:
private Encoding GetEncoding(FileStream stream) { //如果當前流不支持檢索就拋出異常 if (!stream.CanSeek) throw new ArgumentException("require a stream that can seek"); Encoding encoding = Encoding.ASCII; //定義緩沖區,這里只是為了將BOM格式寫入到該字節數組中 byte[] bom = new byte[5]; //從流中讀取字節塊,填充bom字節數組的同時,返回讀入緩沖區的總字節數 //注意流可能小於緩沖區。如果沒有更多的字符可用於讀取,Read()方法就返回0,此時沒有數據寫入到緩沖區 int nRead = stream.Read(bom, offset: 0, count: 5); if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) { Console.WriteLine("UTF-32"); //將該流的當前位置設置為給定值,從流的開始位置起 stream.Seek(4, SeekOrigin.Begin); return Encoding.UTF32; } else if (bom[0] == 0xff && bom[1] == 0xfe) { Console.WriteLine("UTF-16, little endian"); stream.Seek(2, SeekOrigin.Begin); return Encoding.Unicode; } else if (bom[0] == 0xfe && bom[1] == 0xff) { Console.WriteLine("UTF-16,big endian"); stream.Seek(2, SeekOrigin