最近在工作中使用到了文件操作类,但是发现自己对这块基础掌握还是比较薄弱。因此决定对这块内容好好学习,并记录自己的学习理解。
一、目前对于文件处理主要是使用流来操作,那么我们首先了解一下什么是流(stream)。流是一个用于传输数据的对象。
流具有如下3种基本操作:
Stream是虚拟类,它以及它的派生类都提供了Read和Write方法,可以支持在字节级别上对数据进行读写。Read方法从当前字节流读取字节放至内存缓冲区,Write方法把内存缓冲区的字节写入当前流中。
封装的其他流类
但仅支持字节级别的数据处理会给开发人员带来不便。将定应用程序需要将字符数据写入到流中,则需要先将字符数据转化为字节数组之后才能调用Write方法写入流。因此,除了Stream及其派生类的读写方法之外,.Net框架同样提供了其他多种支持流读写的类:
二、常用的文件读写类
1)FileStream (文件流) 主要用于在二进制文件中读取二进制数据
2)StreamReader (流读取器)和StreamWriter (流写入器) 专门用于读取文本格式的流产品
3)BinaryReader和BinaryWriter 专门读写二进制格式的流产品
三、文件读写类的具体使用
1、FileStream
Filestream为文件提供 Stream,既支持同步读写操作,也支持异步读写操作。下面的示例展示了部分 FileStream 构造函数。
var path = @"d:\MyTest.txt";
string str = "看数据会不会被覆盖掉";
byte[] buffer = Encoding.Default.GetBytes(str);
byte[] bufferRead = new byte[1024 * 1024 * 5]; //声明一个5M大小的字节数组
///path参数可以是文件名,包括通用命名约定中的文件 (UNC) 共享。
// 为构造函数提供对文件的读 / 写访问权限,并打开共享读取访问(也就是说,通过此或其他进程打开文件以进行写入的请求将失败,直到该 FileStream 对象关闭,但读取尝试将成功) 。
// 不能使用此构造函数打开只读文件; 相反,必须使用接受 FileAccess 值设置为的参数的构造函数 FileAccess.Read 。
using (FileStream aFile1 = new FileStream(path, FileMode.OpenOrCreate))
{
//向文件中写入数据
//aFile1.Write(buffer);
//读取文件数据
aFile1.Read(bufferRead, 0, bufferRead.Length);
var content = Encoding.Default.GetString(bufferRead);
Console.WriteLine(content);
}
///打开文件并选择对该文件的读/写权限,如果该文件不存在则创建该文件,并且具备对该文件的读写权限.这里要注意你运行的软件需要对该文件夹有访问权限否则会引发异常,显示你无权限。
FileStream aFile2 = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite);
///打开文件并可以设置文件读写权限以及对文件的访问类型
FileStream aFile3 = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite,FileShare.Read);
///这个是一个功能比较强大但是实际使用中较少是的一种构造函数
///share FileShare
// 枚举值的按位组合,这些枚举值确定进程共享文件的方式。
// bufferSize Int32
// 一个大于零的正 Int32 值,表示缓冲区大小。 默认缓冲区大小为 4096。
// useAsync Boolean
FileStream aFile4 = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read, 4096, false);
在Filestream初始化时一般来说需要以下几个基础信息:
(1)FileMode:指定操作系统打开文件的方式。该枚举具有以下属性
Append | 6 | 若存在文件,则打开该文件并查找到文件尾,或者创建一个新文件。 这需要 Append 权限。 |
Create | 2 | 指定操作系统应创建新文件。 如果此文件已存在,则会将其覆盖。 这需要 Write 权限。 |
CreateNew | 1 | 指定操作系统应创建新文件。 这需要 Write 权限。 如果文件已存在,则将引发 IOException异常。 |
Open | 3 | 指定操作系统应打开现有文件。 打开文件的能力取决于 FileAccess 枚举所指定的值。 如果文件不存在,引发一个 FileNotFoundException 异常。 |
OpenOrCreate | 4 | 指定操作系统应打开文件(如果文件存在);否则,应创建新文件。 如果用 |
Truncate | 5 | 指定操作系统应打开现有文件。 该文件被打开时,将被截断为零字节大小。 这需要 Write 权限。 尝试从使用 |
(2)FileAccess:定义文件的读取、写入或读/写访问权限的常量。该枚举具有以下属性
Read | 1 | 对文件的读访问。 可从文件中读取数据。 与 |
ReadWrite | 3 | 对文件的读写访问权限。 可从文件读取数据和将数据写入文件。 |
Write | 2 | 文件的写访问。 可将数据写入文件。 与 |
(3)FileShare:包含用于控制其他 FileStream 对象对同一文件可以具有的访问类型的常数。
Delete | 4 | 允许随后删除文件。 |
Inheritable | 16 | 使文件句柄可由子进程继承。 Win32 不直接支持此功能。 |
None | 0 | 谢绝共享当前文件。 文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。 |
Read | 1 | 允许随后打开文件读取。 如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
ReadWrite | 3 | 允许随后打开文件读取或写入。 如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
Write | 2 | 允许随后打开文件写入。 如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
(4)bufferSize:一个大于零的正 Int32 值,表示缓冲区大小。 默认缓冲区大小为 4096。
(5)useAsync (Boolean):
指定使用异步 I/ O 还是同步 I/ O。 但是,请注意,基础操作系统可能不支持异步 I/ O,因此在指定 true 后,根据所用平台,句柄可能同步打开。当异步打开时,BeginRead(Byte[], Int32, Int32, AsyncCallback, Object) 和 BeginWrite(Byte[], Int32, Int32, AsyncCallback, Object) 方法在执行大量读或写时效果更好,但对于少量的读 / 写,这些方法速度可能要慢得多。 如果应用程序打算利用异步 I/ O,将 useAsync 参数设置为 true。 正确使用异步 I/ O 可以使应用程序的速度加快 10 倍, 但是如果在没有为异步 I / O 重新设计应用程序的情况下使用异步 I/ O,则可能使性能降低 10 倍。
使用 FileStream 类来读取、写入、打开和关闭文件系统上的文件,以及操作与文件相关的其他操作系统句柄,包括管道、标准输入和标准输出。 您可以使用 Read 、 Write 、 CopyTo 和 Flush 方法执行同步操作,或者使用 ReadAsync 、 WriteAsync 、 CopyToAsync 和 FlushAsync 方法执行异步操作。 使用异步方法来执行占用大量资源的文件操作,而不会阻止主线程。 在 Windows 8.x 应用商店 应用或 桌面 应用中一个耗时的流操作可能阻塞 UI 线程并让您的应用看起来好像不工作时,这种性能的考虑就显得尤为重要了。 FileStream 缓冲输入和输出以提高性能。
由于Filestrean操作字节的,因此可以操作任意一种类型的文件,但是与此同时也限制了它日常的使用。因此当你使用write方法时需要先将非byte类型的输入内容进行转换为字节。
2、StreamReader (流读取器)和StreamWriter (流写入器)
(1)StreamReader 设计用于特定编码的字符输入,而 Stream 类用于字节输入和输出。 用于 StreamReader 读取标准文本文件中的行信息。
此类型实现 IDisposable 接口。 在使用完类型后,您应直接或间接释放类型。 若要直接释放类型,请在 try
/catch
块中调用其 Dispose 方法。 若要间接释放类型,请使用 using
(在 C# 中)或 Using
(在 Visual Basic 中)等语言构造。 有关详细信息,请参阅 IDisposable 接口主题中的“使用实现 IDisposable 的对象”一节。
StreamReader 除非另外指定,否则默认为 UTF-8 编码,而不是默认为当前系统的 ANSI 代码页。 UTF-8 正确地处理 Unicode 字符,并为操作系统的本地化版本提供一致的结果。 如果使用属性获取当前字符编码,则 CurrentEncoding 该值在第一种方法之后才是可靠的 Read ,因为在首次调用方法之前不会进行编码自动检测 Read 。默认情况下, StreamReader 不是线程安全的。
BaseStream | 返回基础流。 |
CurrentEncoding | 获取当前 StreamReader 对象正在使用的当前字符编码。 |
EndOfStream | 获取一个值,该值指示当前的流位置是否在流结尾。 |
Close() | 关闭 StreamReader 对象和基础流,并释放与读取器关联的所有系统资源。 |
CreateObjRef(Type) | 创建一个对象,该对象包含生成用于与远程对象进行通信的代理所需的全部相关信息。 (继承自 MarshalByRefObject) |
DiscardBufferedData() | 清除内部缓冲区。 |
Dispose() | 释放由 TextReader 对象使用的所有资源。 (继承自 TextReader) |
Dispose(Boolean) | 关闭基础流,释放 StreamReader 使用的未托管资源,同时还可以根据需要释放托管资源。 |
Equals(Object) | 确定指定对象是否等于当前对象。 (继承自 Object) |
GetHashCode() | 作为默认哈希函数。 (继承自 Object) |
GetLifetimeService() | 已过时。 检索控制此实例的生存期策略的当前生存期服务对象。 (继承自 MarshalByRefObject) |
GetType() | 获取当前实例的 Type。 (继承自 Object) |
InitializeLifetimeService() | 已过时。 获取生存期服务对象来控制此实例的生存期策略。 (继承自 MarshalByRefObject) |
MemberwiseClone() | 创建当前 Object 的浅表副本。 (继承自 Object) |
MemberwiseClone(Boolean) | 创建当前 MarshalByRefObject 对象的浅表副本。 (继承自 MarshalByRefObject) |
Peek() | 返回下一个可用字符,但不使用它。 |
Read() | 读取输入流中的下一个字符并使该字符位置提升一个字符。 |
Read(Char[], Int32, Int32) | 从指定的索引位置开始将来自当前流的指定的最多字符读到缓冲区。 |
Read(Span |
将当前流中的字符读入范围。 |
ReadAsync(Char[], Int32, Int32) | 从当前流中异步读取指定的最大字符,并且从指定的索引位置开始将该数据写入缓冲区。 |
ReadAsync(Memory |
将当前流中的字符异步读入内存块。 |
ReadBlock(Char[], Int32, Int32) | 从当前流中读取指定的最大字符数并从指定的索引位置开始将该数据写入缓冲区。 |
ReadBlock(Span |
从当前流中读取字符并将数据写入缓冲区。 |
ReadBlockAsync(Char[], Int32, Int32) | 从当前流中异步读取指定的最大字符,并且从指定的索引位置开始将该数据写入缓冲区。 |
ReadBlockAsync(Memory |
从当前流中异步读取字符并将数据写入缓冲区。 |
ReadLine() | 从当前流中读取一行字符并将数据作为字符串返回。 |
ReadLineAsync() | 从当前流中异步读取一行字符并将数据作为字符串返回。 |
ReadToEnd() | 读取来自流的当前位置到结尾的所有字符。 |
ReadToEndAsync() | 异步读取来自流的当前位置到结尾的所有字符并将它们作为一个字符串返回。 |
ToString() | 返回表示当前对象的字符串。 (继承自 Object) |
(2)实现一个 TextWriter,使其以一种特定的编码向流中写入字符。
下面的示例演示如何使用 StreamWriter 对象写入一个文件,该文件列出 C 驱动器上的目录,然后使用 StreamReader 对象读取和显示每个目录名称。 一种很好的做法是在语句中使用这些对象, using
以便正确地释放非托管资源。 using
当使用对象的代码已完成时,该语句会自动对 Dispose 该对象调用。
static void Main(string[] args)
{
// Get the directories currently on the C drive.
DirectoryInfo[] cDirs = new DirectoryInfo(@"c:\").GetDirectories();
// Write each directory name to a file.
using (StreamWriter sw = new StreamWriter("CDriveDirs.txt"))
{
foreach (DirectoryInfo dir in cDirs)
{
sw.WriteLine(dir.Name);
}
}
// Read and show each line from the file.
string line = "";
using (StreamReader sr = new StreamReader("CDriveDirs.txt"))
{
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
StreamWriter 专用于特定编码的字符输出,而从派生的类 Stream 则设计用于字节输入和输出。
重要:
此类型实现 IDisposable 接口。 在使用完类型后,您应直接或间接释放类型。 若要直接释放类型,请在 try
/catch
块中调用其 Dispose 方法。 若要间接释放类型,请使用 using
(在 C# 中)或 Using
(在 Visual Basic 中)等语言构造。
StreamWriter 除非另外指定,否则默认为使用实例 UTF8Encoding 。 的此实例 UTF8Encoding
在构造时没有字节顺序标记 (BOM) ,因此其 GetPreamble 方法返回一个空字节数组。 此构造函数的默认 UTF-8 编码对无效字节引发异常。 此行为不同于属性中的编码对象提供的行为 Encoding.UTF8 。 若要指定一个 BOM 并确定无效字节是否引发了异常,请使用接受编码对象作为参数的构造函数,例如 StreamWriter(String, Boolean, Encoding) 或 StreamWriter 。
默认情况下, StreamWriter 不是线程安全的。
AutoFlush | 获取或设置一个值,该值指示 StreamWriter 在每次调用 Write(Char) 之后是否都将其缓冲区刷新到基础流。 |
BaseStream | 获取同后备存储连接的基础流。 |
Encoding | 获取在其中写入输出的 Encoding。 |
FormatProvider | 获取控制格式设置的对象。 (继承自 TextWriter) |
NewLine | 获取或设置由当前 |
C#的FileStream类提供了最原始的字节级上的文件读写功能,但我们习惯于对字符串操作,于是StreamWriter和 StreamReader类增强了FileStream,它让我们在字符串级别上操作文件,但有的时候我们还是需要在字节级上操作文件,却又不是一个字节 一个字节的操作,通常是2个、4个或8个字节这样操作,这便有了BinaryWriter和BinaryReader类,它们可以将一个字符或数字按指定 个数字节写入,也可以一次读取指定个数字节转为字符或数字。例如为了操作图像、压缩文件等二进制流文件,可以使用BinaryReader类和BinaryWriter类,用于二进制模式的读写流。BinaryReader的每个读方法都有一个对应的写方法,比如针对不同的数据结构,BinaryReader类提供了ReadByte、ReadBoolean、ReadInt、ReadInt16、ReadString等,与之对应的BinaryReader类则提供了多个重载的Write方法,分别对应上面的读方法,所以使用起来非常方便。例如,当Write方法传递的参数是Int32类型时,利用BinaryWriter的Write方法可用将Int32类型数据转化为长度为4的字节数组,并将字节流传递给一个Stream对象。
(1)BinaryReader
BinaryReader类提供简化从流中读取基元数据类型的方法。 例如,可以使用 ReadBoolean 方法将下一个字节作为布尔值读取,并将流中的当前位置提升一个字节。 类包含支持不同数据类型的 read 方法。
在创建类的新实例时 BinaryReader ,提供从中读取的流,还可以选择指定编码的类型以及是否在释放对象后保持流处于打开状态 BinaryReader 。 如果未指定编码类型,将使用 UTF-8。
(2)BinaryWriter
BinaryWriter类提供简化将基元数据类型写入流的方法。 例如,可以使用 Write 方法将布尔值作为单字节值写入流。 类包含支持不同数据类型的写入方法。
当你创建类的新实例时 BinaryWriter ,你提供要写入的流,并根据需要指定编码的类型以及是否在释放对象后保持流处于打开状态 BinaryWriter 。 如果未指定编码类型,将使用 UTF-8。