简介
简单总结下IO文件读写的几种方式
- File类适合对不同的对象进行单一的处理。此种特殊情况下,静态方法的调用速度比较快,不用进行实例化
- FileInfo类适合用于对同一文件进行多种操作的情况。此种情况下,实例化后的对象不需要每次都寻找文件,可以直接对该文件进行操作
- File和FileInfo适合用于对文件的创建、删除、属性获取等操作
- FileStream适合处理非文本文件,不适合操作用字符数据构成的文本文件。FileStream类提供了对文件的低级而复杂的操作,因此能够实现更多高级的功能。
- StreamReader && StreamWriter适合处理对文本文件的读写操作,效率高一些
- BinaryReader && BinaryWriter适合处理二进制格式文件的读写操作
两个方式
- using
- 实现对资源的释放,即使异常也会释放(dispose)
- using不用dispose
- try…catch
- 实现对异常的捕获处理
1. DriveInfo 驱动器
在Windows操作系统中,存储介质统称为驱动器,硬盘由于可以划分为多个区域,每一个区域称为一个驱动器。.NET Framework提供DriveInfo类和 DriveType枚举型,以方便在程序中直接使用驱动器。DriveInfo类的常用字段成员有DriveFormat(文件系统格式,如NTFS或FAT32)、DriveType(驱动器类型)、Name(驱动器名)、TotalSize(总空间)、TotalFreeSpace(获得驱动器可用空间)。常用的方法成员有GetDrives(获得可用驱动器列表)。
DriveType枚举型的枚举值有CDRom(光驱)、Fixed(硬盘)、Network(网络驱动器)和Removeable(软盘或U盘)等。例如,以下代码可以输出每一个硬盘驱动器的剩余空间信息。
DriveInfo[] drivers = DriveInfo.GetDrives();
foreach(DriveInfo driver in drivers)
{
if(driver.DriveType == DriveType.Fixed && driver.DriveFormat == "NTFS")
{
Console.WriteLine("在{0}驱动器上还有{1}字节的剩余空间。", driver.Name, driver.AvailableFreeSpace);
}
}
Console.ReadLine();
2. Directory && DirectoryInfo 目录
为了方便检索文件,需要在驱动器中先创建目录,然后把文件保存到这个目录中。在Windows操作系统中,目录又称文件夹。每个驱动器都有一个根目录,使用”\”表示,如”C:\”表示C驱动器的根目录。创建在根目录中的目录称为一级子目录。在一级子目录中创建的目录称为二级子目录,依此类推。文件系统的目录结构是一种树形结构。
.NET Framework提供了Directory类和DirectoryInfo类,以方便在程序中直接操作目录。
Directory类的常用方法成员有CreateDirectory(创建新目录)、Delete(删除目录)、Exists(判断目录是否存在)、Move(移动目录)、GetFiles(获得目录的文件列表)、GetDirectories(获得子目录列表)等。
DirectoryInfo类的常用字段成员有Name(提取目录名)、Exists(判断目录是否存在)、Parent(父目录)、Root(根目录)、MoveTo(移动目录)、GetFiles(获得目录的文件列表)、GetDirectories(获得子目录列表)等。例如,以下代码分别展现了Directory类和DirectoryInfo类的基本方法。
Directory.CreateDirectory(@"d:\C#程序设计");
if(Directory.Exists(@"d:\C#程序设计"))
{
Console.WriteLine("创建成功");
}
Directory.Delete(@"d:\C#程序设计");
if (!Directory.Exists(@"d:\C#程序设计"))
{
Console.WriteLine("删除成功");
}
DirectoryInfo dir = new DirectoryInfo(@"d:\C#程序设计");
if (!dir.Exists)
{
dir.Create();
}
else
{
Console.WriteLine("该目录已经存在");
}
3. File && FileInfo 文件
.NET Framework提供了File类和FileInfo类,以方便在程序中直接操作文件。File和FileInfo类位于System.IO命名空间,都可以用来实现创建、复制、移动、打开文件等操作。File类和FileInfo类与Directory类和DirectoryInfo类的工作方式相似。File类是一个静态类,可直接调用其方法成员。FileInfo类不是静态类,需要先创建实例。
- File
常用API | 介绍 |
---|---|
Open() | 打开文件 |
Copy() | 复制文件 |
Delete() | 删除文件 |
Exists() | 判断文件是否存在 |
Move() | 移动文件 |
Replace() | 替换文件 |
AppendAllText() | 新建文件并添加文本 |
ReadAllText() | 打开并读取文本内容 |
- FileInfo
文件信息类FileInfo与File类不同,它虽然也提供类创建、复制、删除、移动和打开文件的方法,并且帮助创建FileStream对象,但是它提供的仅仅是实例方法
常用API | 介绍 |
---|---|
Name | 提取文件名 |
Directory | 所属目录 |
Exists | 是否存在 |
Extension | 文件扩展名 |
Length | 文件长度 |
IsReadOnly | 是否为只读 |
Open() | 打开文件 |
Create() | 创建文件 |
CopyTo() | 复制到新文件 |
Delete() | 删除文件 |
MoveTo() | 移动文件 |
Replace() | 替换文件 |
EnCrypt() | 加密文件 |
Decrypt() | 解密文件 |
4. 文件流概述
在.NET Framework中,文件和流是有区别的。文件是存储在磁盘上的数据集,它具有名称和相应的路径。当打开一个文件并对其进行读/写时,该文件就称为流(stream)。但是,流不仅仅是指打开的磁盘文件,还可以是网络数据。.Net Framework允许在内存中创建流。此外,在控制台应用程序中,键盘输入和文本显示都是流。流包括以下基本操作:
- 读取(read):把数据从流传输到某种数据结构中,如输出到字符数组中。
- 写入(write):把数据从某种数据结构传输到流中,如把字节数组中的数据传输到流中。
- 定位(seek):在流中查找或重新定位当前位置。
5. FileStream 文件流类
文件流类FileStream公开了以文件为主的Stream,既支持同步读/写操作,也支持异步读/写操作,FileStream类的特点是操作字节和字节数组。这种方式不适合操作用字符数据构成的文本文件,适合处理非文本文件。FileStream类提供了对文件的低级而复杂的操作,因此能够实现更多高级的功能。
读操作:
- 创建流对象
- 定位流位置
- 读取到字节数组
- 编码,转换到字符或字符串
//要写入文件的字符数组
char[] m_cDataWrite = new char[100];
//包含要写入该流的数据的缓冲区
byte[] m_bDataWrite = new byte[100];
try
{
//创建d:\file.txt的FileStream对象
FileStream m_FileStream = new FileStream(@"d:\file.txt", FileMode.Open);
//设置流当前位置为文件开始位置
m_FileStream.Seek(0, SeekOrigin.Begin);
//将文件的内容存到字节数组中(缓存)
m_FileStream.Read(m_bDataWrite, 0, 100);
}
catch (Exception ex)
{
Console.WriteLine("There is an IOException");
Console.WriteLine(ex.Message);
}
//通过UTF-8编码方法将字符数组转换成字符数组
Decoder m_Dec = Encoding.UTF8.GetDecoder();
m_Dec.GetChars(m_bDataWrite, 0, m_bDataWrite.Length, m_cDataWrite, 0);
Console.WriteLine("Read from file Succeed!");
Console.WriteLine(m_cDataWrite);
写操作:
- 创建流对象
- 需要写入的字符数组
- 编码,转换到字节数组
- 设置流位置
- 写入,刷新
//要写入文件的字符数组
char[] m_cDataWrite = new char[100];
//包含要写入该流的数据的缓冲区
byte[] m_bDataWrite = new byte[100];
try
{
//创建d:\file.txt的FileStream对象
FileStream m_FileStream = new FileStream(@"d:\file.txt", FileMode.OpenOrCreate);
//将要写入的字符串转换成字符数组
m_cDataWrite = "test filestream".ToCharArray();
//通过UTF-8编码方法将字符数组转成字节数组
Encoder m_Enc = Encoding.UTF8.GetEncoder();
m_Enc.GetBytes(m_cDataWrite, 0, m_cDataWrite.Length, m_bDataWrite, 0, true);
//设置流当前位置为文件开始位置
m_FileStream.Seek(0, SeekOrigin.Begin);
//将字节数组中的内容写入文件
m_FileStream.Write(m_bDataWrite, 0, m_bDataWrite.Length);
if (m_FileStream != null)
{
//清除此流的缓冲区,使得所有缓冲的数据都写入到文件中
m_FileStream.Flush();
m_FileStream.Close();
}
}
catch (Exception ex)
{
Console.WriteLine("There is an IOException");
Console.WriteLine(ex.Message);
}
Console.WriteLine("Write to File Succeed!");
6. StreamWriter && StreamReader
应用FileStream类需要许多额外的数据类型转换操作,十分影响效率。StreamWriter类允许直接将字符和字符串写入文件。下面演示其用法:
读操作
try
{
//以绝对路径方式构造新的StreamReader对象
StreamReader m_SR = new StreamReader(@"d:\file.txt");
//用ReadToEnd方法将d:\file.txt中的数据全部读入到字符串m_Data中,并关闭StreamReader
string m_Data = m_SR.ReadToEnd();
m_SR.Close();
Console.WriteLine(m_Data);
}
catch (Exception ex)
{
Console.WriteLine("There is an IOException");
Console.WriteLine(ex.Message);
}
写操作
try
{
//保留文件现有数据,以追加写入的方式打开d:\file.txt文件
StreamWriter m_SW = new StreamWriter(@"d:\file.txt", true);
//向文件写入新字符串,并关闭StreamWriter
m_SW.WriteLine("Another File Operation Method");
m_SW.Close();
}
catch (Exception ex)
{
Console.WriteLine("There is an IOException");
Console.WriteLine(ex.Message);
}
7. BinaryReader && BinaryWriter
BinaryReader类是和BinaryWriter类相对应的二进制数据读取类。它用特定的编码将基元数据类型(如:字符串类型)读作二进制值
读操作
FileStream m_FS = new FileStream(@"d:\data.dat", FileMode.Open, FileAccess.Read);
//通过文件流创建相应的BinaryReader
BinaryReader m_BR = new BinaryReader(m_FS);
//从d:\data.dat中读取数据
for(int i = 0; i < 11; i++)
{
Console.WriteLine(m_BR.ReadInt32());
}
m_BR.Close();
m_FS.Close();
Console.ReadLine();
写操作
BinaryWriter类将基础数据(如:字符串)以二进制形式写入文件流中,并支持用特定的编码写入
FileStream m_FS = new FileStream(@"d:\data.dat", FileMode.Create);
//通过文件流创建相应的BinaryWriter
BinaryWriter m_BW = new BinaryWriter(m_FS);
for(int i = 0; i < 11; i++)
{
//向d:\data.dat中写入数据
m_BW.Write((int)i);
}
m_BW.Close();
m_FS.Close();
8. 综合应用
创建日志文件
日志文件的作用是记录程序运行事件。通常使用文本文件保存数据。日志文件需要程序自动创建,并在指定的事件发生时,使用特定的格式把事件的相关数据记录到日志文件中。
- 创建FileStream类实例时,能够通过该类构造函数的参数,指定打开文件的方式和读/写访问的方式。通过指定打开方式,实现日志文件的自动创建。
- 使用StreamWriter类实例写入文件时,因为部分数据可能由于系统缓慢而未能及时写入,所以在所有的写入操作完成后,需要调用Flush方法将缓冲区的文件内容更新到日志文件中。
- 使用StreamWriter类实例写入文件时,写入的方式与Console类似,可以使用WriteLine向文件中写入一行文本数据。
const string _FILENAME = @"..\..\logfile.txt";
static void Main()
{
//从指定的目录以打开或者创建的形式读取日志文件
using (FileStream fs = new FileStream(_FILENAME, FileMode.OpenOrCreate, FileAccess.Write))
{
//创建日志文件的写入流
StreamWriter sw = new StreamWriter(fs);
//向日志文件写入日志信息
Log("日志文件创建成功", sw);
//关闭日志文件写入流
sw.Close();
Console.WriteLine("日志文件已创建");
}
//读取并显示日志文件
using (StreamReader sr = new StreamReader(_FILENAME, Encoding.UTF8))
{
string strContent = sr.ReadToEnd();
sr.Close();
Console.WriteLine(strContent);
}
Console.ReadLine();
}
static void Log(String message, TextWriter tw)
{
tw.Write("Log Entry:");
tw.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
tw.WriteLine(" :");
tw.WriteLine(" :{0}", message);
tw.WriteLine("----------------------------------");
//将缓冲区中的内容更新到日志文件中
tw.Flush();
}
对日志文件的读/写操作
日志文件的读/写和文本文件的读/写方法基本相同,日志文件除了使用StreamReader类和StreamWriter类的实例进行读/写外,还有一些记录事件的要求。例如,在写入数据时使用追加的方式、控制日志文件的大小等。
- 使用FileInfo类实例获取日志文件的大小,实现当日志文件的大小超出指定范围时清空日志数据的功能。并使用该类实例的OpenWrite方法,创建FileStream类实例进行写入文件的操作,实现日志文件的自动创建功能。
- 使用StreamWriter类中定义的Seek方法,将写入位置移动到文件末尾,实现将数据以追加方式写入日志文件的功能。
- 使用StreamReader类中定义的Peek方法,判断读取器是否已经读到日志文件的末尾。
//表示日志文件路径及文件名称的字符串
const string FILENAME = @"..\..\logfile.txt";
static void Main(string[] args)
{
//写入日志信息
WriteLogFile(FILENAME, "日志信息一");
//读取日志文件
Console.WriteLine(ReadLogFile(FILENAME));
Console.ReadLine();
}
static string ReadLogFile(string FileNameWithPath)
{
//从指定的目录以打开或创建的形式读取日志文件
FileStream fs = new FileStream(FileNameWithPath, FileMode.OpenOrCreate, FileAccess.Read);
//定义输出字符串
StringBuilder output = new StringBuilder();
//初始化该字符串的长度为0
output.Length = 0;
//为上面创建的文件流创建读取数据流
StreamReader read = new StreamReader(fs);
//设置当前流的起始位置为文件流的起始点
read.BaseStream.Seek(0, SeekOrigin.Begin);
//读取文件
while(read.Peek() > -1)
{
//取文件的一行内容并换行
output.Append(read.ReadLine() + "\n");
}
//关闭释放读数据流
read.Close();
//返回读到的日志文件内容
return output.ToString();
}
static void WriteLogFile(string FileNameWithPath, string Message)
{
//定义文件信息对象
FileInfo finfo = new FileInfo(FileNameWithPath);
//判断文件是否存在以及是否大于2K
if(finfo.Exists && finfo.Length > 2048)
{
//删除该文件
finfo.Delete();
}
//创建只写文件流
using(FileStream fs = finfo.OpenWrite())
{
//根据上面创建的文件流创建写数据流
StreamWriter w = new StreamWriter(fs);
//设置写数据流的起始位置为文件流的末尾
w.BaseStream.Seek(0, SeekOrigin.End);
//写入"Log Entry:"
w.Write("Log Entry:");
//写入系统的当前时间并换行
w.Write("{0} {1} \r\n", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
//写入日志内容并换行
w.Write(Message + "\r\n");
//写入-------------------------并换行
w.Write("----------------------\r\n");
//清空缓冲区内容,并把缓冲区内容写入基础流
w.Flush();
w.Close();
}
}
复制文件
静态File类中提供了许多操作文件的方法,使用Copy方法复制文件是比较常见的一种操作,调用Copy方法时,可以使用overwrite参数指定是否覆盖文件。
- 使用静态类File的Exists方法判断文件是否存在。
- 使用静态类File的Copy方法实现复制文件的功能,当文件存在时,通过指定override参数覆盖原有文件。
- 复制文件是系统操作,为了保证程序的稳定性,在复制文件的过程中需要捕获并处理异常。
//源文件路径及文件名
const string SOURCEFILENAME = @"..\..\myfile.txt";
//目标文件路径及文件名
const string DESTINATIONFILENAME = @"..\..\result.txt";
static void Main(string[] args)
{
try
{
//判断源文件是否存在
if(!File.Exists(SOURCEFILENAME))
{
Console.WriteLine("找不到源文件");
}
else if (File.Exists(DESTINATIONFILENAME))
{
Console.Write("目标文件已经存在,是否覆盖?(Y/N)");
if(Console.ReadKey(false).Key == ConsoleKey.Y)
{
//覆盖文件
File.Copy(SOURCEFILENAME, DESTINATIONFILENAME, true);
Console.WriteLine("复制文件完成");
}
else
{
Console.WriteLine("取消复制文件");
}
}
else
{
//直接复制
File.Copy(SOURCEFILENAME, DESTINATIONFILENAME);
Console.WriteLine("复制文件完成");
}
}
catch (Exception)
{
Console.WriteLine("复制文件失败");
}
Console.ReadLine();
}
C# 文件操作(摘抄) - wangzhiliang - 博客园 (cnblogs.com)