本文主要介绍C#中的一些文件操作,并介绍如何进行文件读写。
1. 文件信息类
1.1 File和FileInfo类
File
:提供关于文件创建、复制、删除、移动以及打开操作的静态方法,并且帮助创建FileStream
对象;FileInfo
:提供文件属性,以及关于文件创建、复制、删除、移动以及打开操作的实例方法,并且帮助创建FileStream
对象;该类不可以被继承。
案例,以File为例,进行文件的操作:
string path = @"D:\a.txt"; // 文件路径
if (!File.Exists(path)) // 如果文件不存在,则创建该文件
{
File.Create(path).Close(); // 重点!!!
}
File.AppendAllText(path, "hello world"); // 以追加的方式向指定的文件中写入文本
重点:为什么在新建文件后还需要调用Close()
方法呢?
如果我们去掉Close()
方法,直接新建文件然后向文件写入文本,则会产生如下错误:
文件“D:\a.txt”正由另一进程使用,因此该进程无法访问此文件。
原因是File.Create()
后,返回结果是FileStream
,如果不关闭这个文件流,则这个文件就会被一直占用着,导致另外的进程无法访问。
FileInfo
的使用如下:
string path = @"D:\b.txt";
FileInfo fileInfo = new FileInfo(path);
if (!fileInfo.Exists)
{
fileInfo.Create().Close();
}
// FileInfo 没有提供直接向文件写入内容的方法
1.2 Directory和DirectoryInfo类
Directory
:提供创建、移动和便利目录的静态方法,该类不可以被继承;DirectoryInfo
:提供创建、移动和便利目录的实例方法,该类不可以被继承;
案例,演示Directory
的使用:
string directoryPath = @"D:\test"; // 目录路径
if (!Directory.Exists(directoryPath)) // 如果目录不存在,则创建
{
Directory.CreateDirectory(directoryPath);
}
//获取指定目录下的所有文件名,非递归(不包括子目录中的文件名)
string[] fileNames = Directory.GetFiles(directoryPath);
foreach(string filename in fileNames)
{
Console.WriteLine(filename);
}
// 获取指定目录下的子目录名称
string[] directoryNames = Directory.GetDirectories(directoryPath);
foreach(string directoreName in directoryNames)
{
Console.WriteLine(directoreName);
}
1.3 DriveInfo类
DriveInfo
:提供访问驱动信息的入口
DriveInfo[] drives = DriveInfo.GetDrives(); // 获取所有的驱动实例
foreach(DriveInfo drive in drives)
{
Console.WriteLine("Drive Name:{0}",drive.Name); // 驱动器名
Console.WriteLine("Drive Type:{0}", drive.DriveType); // 驱动器类型
if (drive.IsReady)
{
Console.WriteLine(" Volume label: {0}", drive.VolumeLabel);
Console.WriteLine(" File system: {0}", drive.DriveFormat); // 文件系统类型
Console.WriteLine(
" Available space to current user:{0, 15} bytes",
drive.AvailableFreeSpace); // 当前用户可用空间大小
Console.WriteLine(
" Total available space: {0, 15} bytes",
drive.TotalFreeSpace); // 可用空间大小
Console.WriteLine(
" Total size of drive: {0, 15} bytes ",
drive.TotalSize); // 总大小
}
}
2. 流
流(Stream)可以理解为内存中的字节序列。Stream
是所有流的抽象基类,每个具体的存储实体都可以通过Stream
派生类来实现,如FileStream
类就表示文件这种存储实体。
流涉及三种几乎操作:
- 对流进行读取:将流中的数据读取出来;
- 对流进行写入:把数据写入流中;
- 对流进行查找:对流内的当前位置进行查询和修改;
流操作的是字节数据。
我们以FileStream
为例,来演示流的操作:
string filePath = @"D:\stream.txt";
FileStream fileStream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
string msg = "hello world\n你好,世界";
byte[] msgBytes = Encoding.Default.GetBytes(msg);
Console.WriteLine("开始向流中写入数据...");
fileStream.Write(msgBytes, 0, msgBytes.Length);
fileStream.Seek(0,SeekOrigin.Begin);
byte[] readBytes = new byte[msgBytes.Length];
fileStream.Read(readBytes,0,readBytes.Length);
string readStr = Encoding.Default.GetString(readBytes);
Console.WriteLine("从流中读取数据...\n{0}",readStr);
fileStream.Close();
说明:
-
在打开流时,要注意文件权限与文件的打开方式:
文件的权限包括读、写以及读写:
public enum FileAccess { // 对文件的读访问。 可从文件中读取数据。 与 Write 组合以进行读写访问。 Read = 1, // 文件的写访问。 可将数据写入文件。 与 Read 组合以进行读写访问。 Write = 2, // 对文件的读写访问权限。 可从文件读取数据和将数据写入文件。 ReadWrite = 3 }
文件的打开方式在
FileMode
枚举中,包括CreateNew
、Create
、Open
、OpenOrCreate
、Truncate
和Append
:public enum FileMode { // 指定操作系统应创建新文件。 这需要 System.Security.Permissions.FileIOPermissionAccess.Write 权限。 // 如果文件已存在,则将引发 System.IO.IOException异常。 CreateNew = 1, // 指定操作系统应创建新文件。 如果文件已存在,它将被覆盖。 这需要 System.Security.Permissions.FileIOPermissionAccess.Write // 权限。 FileMode.Create 等效于这样的请求:如果文件不存在,则使用 System.IO.FileMode.CreateNew;否则使用 System.IO.FileMode.Truncate。 // 如果该文件已存在但为隐藏文件,则将引发 System.UnauthorizedAccessException异常。 Create = 2, // 指定操作系统应打开现有文件。 打开文件的能力取决于 System.IO.FileAccess 枚举所指定的值。 如果文件不存在,引发一个 System.IO.FileNotFoundException异常 Open = 3, // 指定操作系统应打开文件(如果文件存在);否则,应创建新文件。 // 如果用 FileAccess.Read 打开文件,则需要 System.Security.Permissions.FileIOPermissionAccess.Read权限。 // 如果文件访问为 FileAccess.Write,则需要 System.Security.Permissions.FileIOPermissionAccess.Write权限。 // 如果用 FileAccess.ReadWrite 打开文件,则同时需要 System.Security.Permissions.FileIOPermissionAccess.Read // 和 System.Security.Permissions.FileIOPermissionAccess.Write权限。 OpenOrCreate = 4, // 指定操作系统应打开现有文件。 该文件被打开时,将被截断为零字节大小。 这需要 System.Security.Permissions.FileIOPermissionAccess.Write 权限 // 尝试从使用 FileMode.Truncate 打开的文件中进行读取将导致 System.ArgumentException 异常。 Truncate = 5, // 若存在文件,则打开该文件并查找到文件尾,或者创建一个新文件。 这需要 System.Security.Permissions.FileIOPermissionAccess.Append权限。 // FileMode.Append 只能与 FileAccess.Write 一起使用。 试图查找文件尾之前的位置时会引发 System.IO.IOException异常, // 并且任何试图读取的操作都会失败并引发 System.NotSupportedException 异常。 Append = 6 }
关于文件的权限和文件的读取方式,如源码中的注释。在打开流时,一定要注意打开文件的方式及权限,否则可能会操作失败。
-
fileStream.Seek(long offset, SeekOrigin origin)
方法:该方法的作用时重置流指针的位置,其包含两个参数。public enum SeekOrigin { // 指定流的开始位置。 Begin = 0, // 指定流中的当前位置。 Current = 1, // 指定流的结束位置。 End = 2 }
关于
Seek()
的用法,如下图所示:
表示流指针从SeekOrigin
所表示的位置开始,偏移offset后,流指针到达新位置。offset正数代表向前偏移,负数代表往后偏移。
-
流操作的数据都是字节数据,所以需要将字符串等数据转换成字节数组;
-
操作完成后,需要调用
Close()
方法关闭流;
3. 读写器
由于流操作的是字节数组,不太方便,所以在流的基础上,出现了读写器。读写器通常是成对出现的:一个从流中读取数据,一个用于向流中写入数据。不同类型的读写器分别适用于处理字符串、二进制数据和流等。
字符串读写器:StringReader
、StringWriter
二进制读写器:BinaryReader
、BinaryWriter
流读写器:StreamReader
、StreamWriter
本例演示如何StreamReader
从文件中读取内容:
string filePath = @"D:\stream.txt";
FileStream fileStream = File.OpenRead(filePath);
// 打开流读取器
StreamReader streamReader = new StreamReader(fileStream,Encoding.UTF8);
// 读取文件的全部内容
string msg = streamReader.ReadToEnd();
Console.WriteLine(msg);
// 关闭资源
streamReader.Close();
fileStream.Close();
注意:一定要注意文件的编码格式,否则读取出来的内容会乱码。
参考资料
[1] https://docs.microsoft.com/en-us/dotnet/api/system.io?view=net-5.0
[2] https://blog.csdn.net/jazywoo123/article/details/6981253
[3] StringReader
:https://docs.microsoft.com/en-us/dotnet/api/system.io.stringreader?view=net-5.0
[4] StreamReader
:https://docs.microsoft.com/en-us/dotnet/api/system.io.streamreader?view=net-5.0
[5] 李志 《Learning hard c#学习笔记》