主要内容:
一、路径的相关操作,
如判断路径是否合法,路径类型,路径的特定部分,合并路径,系统文件夹路径等内容;
二、相关通用文件对话框,这些对话框可以帮助我们操作文件系统中的文件和目录;
三、文件、目录、驱动器的操作,如获取它们的基本信息,获取和设置文件和目录的属性,文件的版本信息,
搜索文件和目录,文件判等,复制、移动、删除、重命名文件和目录;
四、读写文件,包括临时文件,随机文件名等;
五、对文件系统的监视;
这一篇就先写一下前两部分。
一、路径相关操作
问题
1
:如何判定一个给定的路径是否有效
/
合法;
解决方案:通过
Path.GetInvalidPathChars
或
Path.GetInvalidFileNameChars
方法获得非法的路径
/
文件名字符,可以
根据它来判断路径中是否包含非法字符;
问题
2
:如何确定一个路径字符串是表示目录还是文件;
解决方案:
1
、使用
Directory.Exists
或
File.Exist
方法,如果前者为真,则路径表示目录;如果后者为真,则路径表示文件;
2
、上面的方法有个缺点就是不能处理那些不存在的文件或目录。这时可以考虑使用
Path.GetFileName
方法获得
其包含的文件名,如果一个路径不为空,而文件名为空那么它表示目录,否则表示文件;
问题
3
:如何获得路径的某个特定部分(如文件名、扩展名等);
解决方案:
下面是几个相关方法:
Path.GetDirectoryName
:返回指定路径字符串的目录信息;
Path.GetExtension
:
返回指定的路径字符串的扩展名;
Path.GetFileName
:
返回指定路径字符串的文件名和扩展名;
Path.GetFileNameWithoutExtension
:返回不具有扩展名的路径字符串的文件名;
Path.GetPathRoot
:获取指定路径的根目录信息;
(更多内容还请参考
MSDN
)
问题
4
:如何准确地合并两个路径而不用去担心那个烦人的
”/”
字符;
解决方案:
使用
Path.Combine
方法,它会帮你处理烦人的
”/”
;
问题
5
:如何获得系统目录的的路径(如桌面,我的文档,临时文件夹等);
解决方案:
主要是使用
System. Environment
类的相关属性和方法:
Environment. SystemDirectory
属性:获取系统目录的完全限定路径;
Environment. GetFolderPath
方法:该方法接受的参数类型为
Environment.SpecialFolder
枚举,
通过这个方法可以获得大量系统文件夹的路径,如我的电脑,我的电脑,桌面,系统目录等;
(更多内容还请参考
MSDN
);
Path.GetTempPath
方法:返回当前系统的临时文件夹的路径;
问题
6
:如何判断一个路径是绝对路径还是相对路径;
解决方案:
使用
Path.IsPathRooted
方法;
问题
7
:如何读取或设置当前目录;
解决方案:
使用
Directory
类的
GetCurrentDirectory
和
SetCurrentDirectory
方法;
问题
8
:如何使用相对路径;
解决方案:
设置当前目录后(见问题
7
),就可以使用相对路径了。对于一个相对路径,我们可以
使用
Path.GetFullPath
方法获得它的完全限定路径(绝对路径)。
注意:如果打算使用相对路径,建议你将工作目录设置为各个交互文件的共同起点,否则可能会引入
一些不易发现的安全隐患,被恶意用户利用来访问系统文件。
更多内容:
通常我们可以使用
System.IO.Path
类来处理路径。该类提供了一套方法和属性用于对包含文件或目录路径信息的字符串执行操作,这些操作是以跨平台的方式执行的,而这些方法和属性都是静态的。
注意路径仅仅是提供文件或目录位置的字符串。路径不必指向磁盘上的位置,例如,路径可以映射到内存中或设备上的位置。路径的准确格式是由当前平台确定的。例如,在某些系统上,路径可以驱动器号或卷号开始,而此元素在其他系统中是不存在的。在某些系统上,文件路径可以包含扩展名,扩展名指示在文件中存储的信息的类型。文件扩展名的格式是与平台相关的;例如,某些系统将扩展名的长度限制为
3
个字符,而其他系统则没有这样的限制。当前平台还确定用于分隔路径中各元素的字符集,以及确定在指定路径时不能使用的字符集。因为这些差异,所以
Path
类的字段以及
Path
类的某些成员的准确行为是与平台相关的。
路径可以包含绝对或相对位置信息。绝对路径完整指定一个位置:文件或目录可被唯一标识,而与当前位置无关。相对路径指定部分位置:当定位用相对路径指定的文件时,当前位置用作起始点。
Path
类的大多数成员不与文件系统交互,并且不验证路径字符串指定的文件是否存在。修改路径字符串的
Path
类成员(例如
ChangeExtension
)对文件系统中文件的名称没有影响。但
Path
成员确实验证指定路径字符串的内容;并且如果字符串包含在路径字符串中无效的字符(如
InvalidPathChars
中的定义),则引发
ArgumentException
异常。例如,在基于
Windows
的桌面平台上,无效路径字符可能包括引号
(")
、小于号
(<)
、大于号
(>)
、管道符号
(|)
、退格
(/b)
、空
(/0)
以及从
16
到
18
和从
20
到
25
的
Unicode
字符。
Path
类的成员使您可以快速方便地执行常见操作,例如确定文件扩展名是否是路径的一部分,以及将两个字符串组合成一个路径名。
多数情况下,如果这些方法接收了无效的路径会抛出异常,但如果路径名是因为包含了通配符(
*
或
?
)从而无效,则不会抛出异常(可以使用
GetInvalidPathChars
方法来非法的路径字符)。我们可以根据该原则判断一个路径是否合法。
二、相关的通用文件对话框
1
、文件夹浏览对话框(
FolderBrowserDialog
类)
主要属性:
Description
:树视图控件上显示的说明文本,如上图中的
”
选择要进行计算的目录
”
;
RootFolder
:获取或设置从其开始浏览的根文件夹,如上图中设置的我的电脑(默认为桌面);
SelectedPath
:获取或设置用户选定的路径,如果设置了该属性,打开对话框时会定位到指定路径,默认为根文件夹,关闭对话框时根据该属性获取用户用户选定的路径;
ShowNewFolderButton
:获取或设置是否显示新建对话框按钮;
主要方法:
ShowDialog
:打开该对话框,返回值为
DialogResult
类型值,如果为
DialogResult.OK
,则可以由
SelectedPath
属性获取用户选定的路径;
dlgOpenFolder.Description =
"
选择要进行计算的目录
";
dlgOpenFolder.RootFolder = Environment.SpecialFolder.MyComputer;
dlgOpenFolder.ShowNewFolderButton =
true
;
DialogResult result = dlgOpenFolder.ShowDialog(
this
);
if
(result == DialogResult.OK)
{
txtDirPath.Text = dlgOpenFolder.SelectedPath;
}
2
、打开文件对话框(
OpenFileDialog
类)
主要属性:
CheckFileExists:该值指示如果用户指定不存在的文件名,对话框是否显示警告;
FileName(s):获取或设置一个包含在文件对话框中选定的文件名的字符串;
Filter:获取或设置对话框的文件类型列表;
FilterIndex:对话框的文件类型列表的索引(基于1的);
InitialDirectory:获取或设置文件对话框显示的初始目录;
Multiselect:该值指示对话框是否允许选择多个文件;
ShowReadOnly:该值指示对话框是否包含只读复选框;
Title:获取或设置文件对话框标题;
主要方法:
OpenFile:打开用户选定的具有只读权限的文件;
ShowDialog:打开该模式对话框;
dlgOpenFile.Title ="打开源文件";
dlgOpenFile.InitialDirectory =@"C:/Inetpub/";
dlgOpenFile.Filter ="文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*";
dlgOpenFile.FilterIndex =2;
dlgOpenFile.ShowReadOnly =true;
DialogResult dr = dlgOpenFile.ShowDialog();
if (dr == DialogResult.OK)
{
string fileName = dlgOpenFile.FileName;
}
3、保存文件对话框(SaveFileDialog类)
主要属性:
大部分与打开文件对话框类似,此处略过,下面几个值得注意:
CreatePrompt:该值指示如果用户指定不存在的文件,是否提示用户允许创建该文件;
OverwritePrompt:该值指示如果用户指定的文件名已存在,对话框是否显示警告;
主要方法:
OpenFile:打开用户选定的具有读/写权限的文件;
ShowDialog:打开该模式对话框;
示例代码:
dlgSaveFile.Title ="打开目标文件";
dlgSaveFile.InitialDirectory =@"C:/Inetpub/";
dlgSaveFile.Filter ="文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*";
dlgSaveFile.FilterIndex =2;
DialogResult dr = dlgSaveFile.ShowDialog();
if (dr == DialogResult.OK)
{
string fileName = dlgSaveFile.FileName;
}
至此,我们操作的都只是路径,要知道,这些路径仅仅是字符串,还没有涉及到文件系统中的真实文件。下一篇中会详细了解有关文件和目录的相关操作。
摘要:
文件操作是程序中非常基础和重要的内容,而路径、文件、目录以及
I/O
都是在进行文件操作时的常见主题,这里想把这些常见的问题作个总结,对于每个问题,尽量提供一些解决方案,即使没有你想要的答案,也希望能提供给你一点有益的思路,如果你有好的建议,恳请能够留言,使这些内容更加完善。
主要内容:
一、路径的相关操作,
如判断路径是否合法,路径类型,路径的特定部分,合并路径,系统文件夹路径等内容;
二、相关通用文件对话框,这些对话框可以帮助我们操作文件系统中的文件和目录;
三、文件、目录、驱动器的操作,如获取它们的基本信息,获取和设置文件和目录的属性,文件的版本信息,
搜索文件和目录,文件判等,复制、移动、删除、重命名文件和目录;
四、读写文件,包括临时文件,随机文件名等;
五、对文件系统的监视;
上一篇介绍了第一、二部分,这一篇介绍一下最重要的第三部分。
三、文件和目录相关操作
文件和目录操作涉及的类主要是:
FileInfo,DirectoryInfo,DriveInfo
,可以认为它们的一个实例对应着一个文件、目录、驱动器。它们的用法类似,一般是将文件、目录或驱动器的路径作为参数传递给相应的构造函数创建一个实例,然后访问它们的属性和方法。
注意下面几点:
FileInfo
类和
DirectoryInfo
类都继承自抽象类
FileSystemInfo
,
FileSystemInfo
类定义了一些通用的属性,如
CreationTime
、
Exists
等。但
DriveInfo
类没有继承
FileSystemInfo
类,所以它也就没有上面提到的那些通用属性了。
FileInfo
类和
DirectoryInfo
类的对象公开的属性值都是第一次查询时获取的值,如果在以此查询之后文件或目录发生了改动,就必须调用它们的
Refresh
方法来更新这些属性。但
DriveInfo
则无需这么做,它的属性每次都会读取文件系统最新的信息。
在创建文件、目录或驱动器的实例时,如果使用了一个不存在的路径,并不会报错,这是你得到一个对象,该对象表示一个并不存在的实体,这意味着它的
Exists
属性(对于
DriveInfo
来说是
IsReady
属性)值为
false
。你仍然可以操作该实体,但如果尝试其它的大多数属性,就会引发相应的
FileNotFoundException
、
DirectoryNotFoundException
或
DriveNotFoundException
异常。
另外,还可以使用
File / Directory
类,这两个类的成员都是静态方法,所以如果只想执行一个操作,那么使用
File/Directory
中的静态方法的效率比使用相应的
FileInfo / DirectoryInfo
中的
实例方法可能更高。所有的
File / Directory
方法都要求当前所操作的文件
/
目录的路径。
注意:
File / Directory
类的静态方法对所有方法都执行安全检查。如果打算多次重用某个对象,可考虑改用
FileInfo / DirectoryInfo
的相应实例方法,因为并不总是需要安全检查。
下面是一些常见的问题:
问题
1
:如何获取指定文件的基本信息;
解决方案:可以使用
FileInfo
类的相关属性:
FileInfo.Exists
:获取指定文件是否存在;
FileInfo.Name
,
FileInfo.Extensioin
:获取文件的名称和扩展名;
FileInfo.FullName
:获取文件的全限定名称(完整路径);
FileInfo.Directory
:获取文件所在目录,返回类型为
DirectoryInfo
;
FileInfo.DirectoryName
:获取文件所在目录的路径(完整路径);
FileInfo.Length
:获取文件的大小(字节数);
FileInfo.IsReadOnly
:获取文件是否只读;
FileInfo.Attributes
:获取或设置指定文件的属性,返回类型为
FileAttributes
枚举,可以是多个值的组合(见问题
2
);
FileInfo.CreationTime
、
FileInfo.LastAccessTime
、
FileInfo.LastWriteTime
:分别用于获取文件的创建时间、访问时间、修改时间;
(更多内容还请参考
MSDN
)
问题
2
:如何获取和设置文件的属性,比如只读、存档、隐藏等;
解决方案:
使用
FileInfo.Attributes
属性可以获取和设置文件的属性,该属性类型为
FileAttributes
枚举,该枚举的每个值表示一种属性,
FileAttributes
枚举具有属性(
Attribute
)
FlagsAttribute
,所以该枚举的值可以进行组合,也就是一个文件可以同时拥有多个属性。下面看看具体的做法:
获取属性,比如判断一个文件是否是只读的:
//
当文件具有其它属性时,这种做法会失败
if
(file.Attributes == FileAttributes.ReadOnly)
{
chkReadonly.Checked =
true
;
}
//
这种写法就不会有问题了,它只检查只读属性
if
((file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
chkReadonly.Checked =
true
;
}
设置属性,比如添加和移除一个文件的只读属性:
if
(chkReadonly.Checked)
{
//
添加只读属性
file.Attributes |= FileAttributes.ReadOnly;
}
else
{
//
移除只读属性
file.Attributes &=~FileAttributes.ReadOnly;
}
问题
3
:如何获取文件的版本信息(比如版本号,版权声明,公司名称等);
解决方案:
使用
FileVersionInfo
类,该类有大量的版本信息相关的属性。通过它的静态方法
GetVersionInfo
获得该类的一个实例,然后就可以访问指定文件的版本信息了,非常方便。如
FileVersion
表示文件版本号,
LegalCopyright
表示指定文件的版权声明,
CompanyName
表示指定文件的公司名称。(更多内容还请参考
MSDN
)
问题
4
:如何判断两个文件的内容是否相同(精确匹配);
解决方案:
使用
System.security.Cryptography.HashAlgorithm
类为每个文件生成一个哈希码,然后比较两个哈希码是否一致。
在比较文件内容的时候可以采用好几种方法。例如,检查文件的某一特定部分是否一致;如果愿意,你甚至可以逐字节读取文件,逐字节进行比较。这两种方法都是可以的,但在某些情况下,还是使用哈希码算法更为方便。
该算法为一个文件生成一个小的(通常约为
20
字节)二进制
”
指纹
”
(
binary fingerprint
)。从统计学角度看,不同的文件不可能生成相同的哈希码。事实上,即使是一个很小的改动(比如,修改了源文件中的一个
bit
),也会有
50
%的几率来改变哈希码中的每一个
bit
。因此,哈希码常常用于数据安全方面。
要生成一个哈希码,你必须首先创建一个
HashAlgorithm
对象,而这通常是调用
HashAlgorithm.Create
方法来完成的;然后调用
HashAlgorithm.ComputeHash
方法,它会返回一个存储哈希码的字节数组。代码如下:
///
///
判断两个文件内容是否一致
///
Public
static bool IsFilesEqual(string fileName1, string fileName2)
{
using
(HashAlgorithm hashAlg = HashAlgorithm.Create())
{
using
(FileStream fs1 =
new
FileStream(fileName1, FileMode.Open), fs2 =
new
FileStream(fileName2, FileMode.Open))
{
byte
[] hashBytes1 = hashAlg.ComputeHash(fs1);
byte
[] hashBytes2 = hashAlg.ComputeHash(fs2);
//
比较哈希码
return
(BitConverter.ToString(hashBytes1) == BitConverter.ToString(hashBytes2));
}
}
}
问题
5
:如何获取指定目录的基本信息;
解决方案:可以使用
DirectoryInfo
类的相关属性和方法:
DirectoryInfo.Exists
:获取指定目录是否存在;
DirectoryInfo.Name
:获取目录的名称;
DirectoryInfo.FullName
:获取目录的全限定名称(完整路径);
DirectoryInfo.Attributes
:获取或设置指定目录的属性,返回类型为
FileAttributes
枚举,可以是多个值的组合;
DirectoryInfo.CreationTime
、
FileInfo.LastAccessTime
、
FileInfo.LastWriteTime
:分别用于获取目录的创建时间、访问时间、修改时间;
DirectoryInfo.Parent
:获取目录的上级目录,返回类型为
DirectoryInfo
;
DirectoryInfo.Root
:获取目录的根目录,返回类型为
DirectoryInfo
;
问题
6
:如何获取指定目录包含的文件和子目录;
解决方案:
DirectoryInfo.GetFiles()
:获取目录中(不包含子目录)的文件,返回类型为
FileInfo[]
,支持通配符查找;
DirectoryInfo.GetDirectories()
:获取目录(不包含子目录)的子目录,
返回类型为
DirectoryInfo[]
,支持通配符查找;
DirectoryInfo. GetFileSystemInfos()
:获取指定目录下(不包含子目录)的文件和子目录,
返回类型为
FileSystemInfo[]
,支持通配符查找;
问题
7
:如何获得指定目录的大小;
解决方案:
检查目录内的所有文件,利用
FileInfo.Length
属性获取每个文件的大小,然后进行合计,然后使用递归算法处理所有的子目录的文件,参考下面代码:
///
///
计算一个目录的大小
///
///
///
///
Private
long CalculateDirSize(DirectoryInfo di, bool includeSubDir)
{
long
totalSize =0;
//
检查所有(直接)包含的文件
FileInfo[] files = di.GetFiles();
foreach
(FileInfo file
in
files)
{
totalSize += file.Length;
}
//
检查所有子目录,如果
includeSubDir
参数为
true
if
(includeSubDir)
{
DirectoryInfo[] dirs = di.GetDirectories();
foreach
(DirectoryInfo dir
in
dirs)
{
totalSize += CalculateDirSize(dir, includeSubDir);
}
}
return
totalSize;
}
问题
8
:如何使用通配符搜索指定目录内的所有文件;
解决方案:
使用
DirectoryInfo.GetFiles
方法的重载版本,它可以接受一个过滤表达式,返回
FileInfo
数组,另外它的参数还可以指定是否对子目录进行查找。如:
dir.GetFiles("*.txt"
,
SearchOption.AllDirectories);
问题
9
:如何复制、移动、重命名、删除文件和目录;
解决方案:使用
FileInfo
和
DirectoryInfo
类。
下面是
FileInfo
类的相关方法:
FileInfo.CopyTo
:将现有文件复制到新文件,其重载版本还允许覆盖已存在文件;
FileInfo.MoveTo
:将指定文件移到新位置,并提供指定新文件名的选项,所以可以用来重命名文件(而不改变位置);
FileInfo.Delete
:永久删除文件,如果文件不存在,则不执行任何操作;
FileInfo.Replace
:使用当前
FileInfo
对象对应文件的内容替换目标文件,而且指定另一个文件名作为被替换文件的备份,微软考虑实在周到。
下面是
DirectoryInfo
类的相关方法:
DirectoryInfo.Create
:创建指定目录,如果指定路径中有多级目录不存在,该方法会一一创建;
DirectoryInfo.CreateSubdirectory
:创建当前对象对应的目录的子目录;
DirectoryInfo.MoveTo
:将目录(及其包含的内容)移动至一个新的目录,也可用来重命名目录;
DirectoryInfo.Delete
:删除目录(如果它存在的话)。如果要删除一个包含子目录的目录,要使用它的重载版本,以指定递归删除。
注意到了没有?
DirectoryInfo
类少了一个
CopyTo
方法,不过我们可以通过递归来实现这个功能:
///
///
复制目录到目标目录
///
///
///
Public
static void CopyDirectory(DirectoryInfo source, DirectoryInfo destination)
{
//
如果两个目录相同,则无须复制
if
(destination.FullName.Equals(source.FullName))
{
return
;
}
//
如果目标目录不存在,创建它
if
(!destination.Exists)
{
destination.Create();
}
//
复制所有文件
FileInfo[] files = source.GetFiles();
foreach
(FileInfo file
in
files)
{
//
将文件复制到目标目录
file.CopyTo(Path.Combine(destination.FullName, file.Name),
true
);
}
//
处理子目录
DirectoryInfo[] dirs = source.GetDirectories();
foreach
(DirectoryInfo dir
in
dirs)
{
string
destinationDir = Path.Combine(destination.FullName, dir.Name);
//
递归处理子目录
CopyDirectory(dir,
new
DirectoryInfo(destinationDir));
}
}
问题
10
:如何获得计算机的所有逻辑驱动器;
解决方案:使用
DriveInfo
类(需要
.NET 2.0
)
DriveInfo.GetDrives()
:获得计算机的所有逻辑驱动器,返回类型为
DriveInfo[]
;
问题
11
:如何获取指定驱动器的信息;
解决方案:
DriveInfo.Name
:获取驱动器的名称(如
C:/
);
DriveInfo.DriveType
:获取驱动器的类型(如
Fixed
,
CDRom
,
Removable
,
Network
等);
DriveInfo.DriveFormat
:获取驱动器的格式(如
NTFS
,
FAT32
,
CDFS
,
UDF
等);
DriveInfo.IsReady
:获取驱动器是否已准备好,比如
CD
是否已放入
CD
驱动器,如果驱动器没有准备好,访问其信息会引发
IOException
类型异常;
DriveInfo.AvailableFreeSpace
:获取驱动器的可用空间;
DriveInfo.TotalFreeSpace
:获取驱动器总的可用空间,它与
AvailableFreeSpace
的不同在于
AvailableFreeSpace
会磁盘配额的设置;
DriveInfo.TotalSize
:获取驱动器总的空间;
DriveInfo.RootDirectory
:获得驱动器的根目录(
DirectoryInfo
类型);
至此,我们已经了解了文件和目录相关的一些基本操作。但还不清楚如何去读写文件的内容,下一篇中会详细了解这方面的操作。
路径,文件,目录,I/O常见操作汇总(三)
主要内容:
一、路径的相关操作,如判断路径是否合法,路径类型,路径的特定部分,合并路径,系统文件夹路径等内容;
二、相关通用文件对话框,这些对话框可以帮助我们操作文件系统中的文件和目录;
三、文件和目录操作,如复制、移动、删除、重命名,文件的版本信息,文件判等、搜索,读写文件等;
四、读写文件,对文件系统的监视;
五、其它,如临时文件,随机文件名等;
文件读写相关类介绍:
文件读写操作涉及的类主要是:
MarshalByRefObject
类:允许在支持远程处理的应用程序中跨应用程序域边界访问对象;
BinaryReader
类:用特定的编码将基元数据类型读作二进制值。
BinaryWriter
类:
以二进制形式将基元类型写入流,并支持用特定的编码写入字符串。
Stream
类:
提供字节序列的一般视图。
FileStream
类:公开以文件为主的
Stream
,既支持同步读写操作,也支持异步读写操作。
MemoryStream
类:创建其支持存储区为内存的流。
BufferedStream
类:给另一流上的读写操作添加一个缓冲层。
TextReader
类:表示可读取连续字符系列的阅读器。
TextWriter
类:表示可以编写一个有序字符系列的编写器。
StreamReader
类:实现一个
TextReader
,使其以一种特定的编码从字节流中读取字符。
StreamWriter
类:实现一个
TextWriter
,使其以一种特定的编码向流中写入字符。
StringReader
类:实现从字符串进行读取的
TextReader
。
StringWriter
类:实现一个用于将信息写入字符串的
TextWriter
。该信息存储在基础
StringBuilder
中。
在使用它们之前最好能了解它们的继承关系,有助于作出最合适的选择:
另外还要注意一下
FileInfo
和
File
类的一些方法,如
Create
,
CreateText
,
Open
等,有时也会带来方便。
这些类的内容比较繁多,更多内容还请参考
MSDN
。
下面是一些常见的问题及其解决方案:
问题
1
:如何读写文本文件(并考虑不同的编码类型);
解决方案:
创建一个
FileStream
对象用以引用该文件。要写入文件,将
FileStream
对象封装在
StreamWriter
对象中,使用其重载了的
Write
方法;要读取文件,将
FileStream
对象封装在
StreamReader
对象中,使用其
Read
或
ReadLine
方法;
.NET Framework
允许通过
StreamWriter
和
StreamReader
类操作任何流来读写文本文件。当使用
StreamWriter
类写入数据时,调用它的
Write
方法,该方法在重载后可以支持所有常见的
C#
数据类型,包括字符串、字符、整数、浮点数以及十进制数等。但
Write
方法总会将的得到的数据转换为文本,如果希望将这些文本转换回原来的数据类型,应使用
WriteLine
方法,以确保每个值都处于单独的一行上。
字符串的表现形式取决于你使用的编码,最常见的编码类型包括下面几种:
ASCII
,
UTF-16
,
UTF-7
,
UTF-8
。
.NET Framework
在
System.Text
命名空间中为每种编码类型提供了一个类。在使用
StreamWriter
和
StreamReader
类时,可以指定需要的编码类型,或者使用默认的
UTF-8
。
而在读取文本文件时,则要使用
StreamReader
类的
Read
或
ReadLine
方法。
Read
方法读取单个字符或者指定个数的字符,返回类型为字符或字符数组;
ReadLine
方法则返回包含整行内容的字符串;
ReadToEnd
方法从当前位置读取至流的结尾。
(更多内容还请参考
MSDN
)
写入文本文件的示例:
using
(FileStream fs =
new
FileStream(fileName, FileMode.Create))
{
//
创建一个
StreamWriter
对象,使用
UTF-8
编码格式
using
(StreamWriter writer =
new
StreamWriter(fs, Encoding.UTF8))
{
//
分别写入十进制数,字符串和字符类型的数据
writer.WriteLine(123.45M);
writer.WriteLine("String Data");
writer.WriteLine('A');
}
}
读取文本文件的示例:
//
以只读模式打开一个文本文件
using
(FileStream fs =
new
FileStream(fileName, FileMode.Open))
{
using
(StreamReader reader =
new
StreamReader(fs, Encoding.UTF8))
{
string
text =
string
.Empty;
while
(!reader.EndOfStream)
{
text = reader.ReadLine();
txtMessage.Text += text + Environment.NewLine;
}
}
}
问题
2
:如何读写二进制文件(使用强数据类型);
解决方案:
创建一个
FileStream
对象用以引用该文件。要写入文件,将
FileStream
对象封装在
BinaryWriter
对象中,使用其重载了的
Write
方法;要读取文件,将
FileStream
对象封装在
BinaryReader
对象中,使用相应数据类型的
Read
方法。
.NET Framework
允许通过
BinaryWriter
和
BinaryReader
类操作任何流来读写二进制数据。当使用
BinaryWriter
类写入数据时,调用它的
Write
方法,该方法在重载后可以支持所有常见的
C#
数据类型,包括字符串、字符、整数、浮点数以及十进制数等,然后数据会被编码为一系列字节写入文件,也可以配置该过程中的编码类型。
在使用二进制文件时,一定要特别注意其中的数据类型。当你读取数据时,一定要使用
BinaryReader
类的某种强类型的
Read
方法。例如,要读取字符串,要使用
ReadString
方法。(
BinaryWriter
在写入二进制文件时总会记录字符串的长度以避免任何可能的错误)
写入文件的示例:
using
(FileStream fs =
new
FileStream(fileName, FileMode.Create))
{
using
(BinaryWriter writer =
new
BinaryWriter(fs))
{
//
写入十进制数,字符串和字符
writer.Write(234.56M);
writer.Write("String");
writer.Write('!');
}
}
读取文件的示例:
//
以只读模式打开一个二进制文件
using
(FileStream fs =
new
FileStream(fileName, FileMode.Open))
{
using
(StreamReader sr =
new
StreamReader(fs))
{
MessageBox.Show("
全部数据:
"+ sr.ReadToEnd());
fs.Position =0;
using
(BinaryReader reader =
new
BinaryReader(fs))
{
//
选用合适的数据类型读取数据
string
message = reader.ReadDecimal().ToString() + Environment.NewLine;
message += reader.ReadString() + Environment.NewLine;
message += reader.ReadChar().ToString();
MessageBox.Show(message);
}
}
}
问题
3
:如何异步读取文件;
解决方案:
有时你需要读取一个文件但又不希望影响程序的执行。常见的情况是读取一个存储在网络驱动器上的文件。
FileStream
提供了对异步操作的基本支持,即它的
BeginRead
和
EndRead
方法。使用这些方法,可以在
.NET Framework
线程池提供的线程中读取一个数据块,而无须直接与
System.Threading
命名空间中的线程类打交道。
采用异步方式读取文件时,可以选择每次读取数据的大小。根据情况的不同,你可能会每次读取很小的数据(比如,你要将数据逐块拷贝至另一个文件),也可能是一个相对较大的数据(比如,在程序逻辑开始之前需要一定数量的数据)。在调用
BeginRead
时指定要读取数据块的大小,同时传入一个缓冲区(
buffer
)以存放数据。因为
BeginRead
和
EndRead
需要访问很多相同的信息,如
FileStream
,
buffer
,数据块大小等,因此将这些内容封装一个单独的类当中是一个好主意。
下面这个类就是一个简单的示例。
AsyncProcessor
类提供了
StartProcess
方法,调用它开始读取,每次读取操作结束,
OnCompletedRead
回调函数会被触发,此时可以处理数据,如果还有剩余数据,则开始一个新的读取操作。默认情况下,
AsyncProcessor
类每次读取
2KB
数据。
class
AsyncProcessor
{
private
Stream inputStream;
//
每次读取块的大小
Private
int bufferSize =2048;
Public
int BufferSize
{
get
{
return
bufferSize; }
set
{ bufferSize = value; }
}
//
容纳接收数据的缓存
Private
byte[] buffer;
public
AsyncProcessor(
string
fileName)
{
buffer =
new
byte
[bufferSize];
//
打开文件,指定参数为
true
以提供对异步操作的支持
inputStream =
new
FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize,
true
);
}
Public
void StartProcess()
{
//
开始异步读取文件,填充缓存区
inputStream.BeginRead(buffer, 0, buffer.Length, OnCompletedRead,
null
);
}
Private
void OnCompletedRead(IAsyncResult asyncResult)
{
//
已经异步读取一个
块
,接收数据
int
bytesRead = inputStream.EndRead(asyncResult);
//
如果没有读取任何字节,则流已达文件结尾
if
(bytesRead >0)
{
//
暂停以模拟对数据块的处理
Debug.WriteLine("
异步线程:已读取一块
");
Thread.Sleep(TimeSpan.FromMilliseconds(20));
//
开始读取下一块
inputStream.BeginRead(buffer, 0, buffer.Length, OnCompletedRead,
null
);
}
else
{
//
结束操作
Debug.WriteLine("
异步线程:读取文件结束
");
inputStream.Close();
}
}
}
使用该类时可以这么写:
//
开始在另一线程中异步读取文件
AsyncProcessor asyncIO =
new
AsyncProcessor("test.txt");
asyncIO.StartProcess();
//
在主程序中,做其它事情,这里简单地循环
10
秒
DateTime startTime = DateTime.Now;
while
(DateTime.Now.Subtract(startTime).TotalSeconds <
10)
{
Debug.WriteLine("
主程序:正在进行
");
//
暂停线程以模拟耗时的操作
Thread.Sleep(TimeSpan.FromMilliseconds(100));
}
Debug.WriteLine("
主程序:已完成
");
问题
4
:如何创建临时文件;
解决方案:
有时需要在特定用户的临时目录下创建一个临时文件,这要求该文件具有唯一的名称,避免与其它程序生成的临时文件相冲突。我们会有多种选择。最简单的是,在程序所在目录内使用
GUID
或时间戳加上随机值作为文件名称。但
Path
类提供的方法还是可以为你节省工作量,这就是它的静态
GetTempFileName
方法,它在当前用户的临时目录下创建一个临时文件(文件名称一定是唯一的),临时目录通常类似于这样:
C:/Documents and Settings/[username]/Local Settings/Temp
。
string
tempFile = Path.GetTempFileName();
using
(FileStream fs =
new
FileStream(tempFile, FileMode.Open))
{
using
(BinaryWriter writer =
new
BinaryWriter(fs))
{
//
写入数据
writer.Write("
临时文件信息
");
}
}
// Do something
//
最后删除临时文件
File.Delete(tempFile);
问题
5
:如何获得随机文件名;
解决方案:
使用
Path.GetRandomFileName
方法,它与
GetTempFileName
方法的不同之处在于它仅仅返回一个字符串但不会创建文件。
问题
6
:监视文件系统的变化;
解决方案:
如果指定路径内的文件发生改变(比如文件被修改或创建),你希望能对此作出反应。
如果程序与其它多个程序或业务处理相关,常常需要创建一个程序,并且只有文件系统发生变化时它才处于活动状态。你可以创建一个这样的程序,让它定期区检测指定目录,此时会发现有件事情让你苦恼:检测得越频繁,就会浪费越多的系统资源;而检测得越少,那么检测到变化的时间就会越长。
这时可以使用
FileSystemWatcher
组件,指定要进行监视的目录或文件,并处理其
Created
,
Deleted
,
Renamed
,
Changed
事件。
要使用
FileSystemWatcher
组件,首先要创建它的一个实例,然后设置下列属性:
Path
:指定要监视的目录;
Filter
:指定要监视的文件类型,如
”*.txt”
;
NotifyFilter
:指定要监视的变化类型;
FileSystemWatcher
会引发四个关键的事件:
Created
,
Deleted
,
Renamed
,
Changed
。这些事件都在其
FileSystemEventArgs
参数中提供了相关文件的信息:如文件名,路径,改变类型,
Renamed
事件中还可以了解到改变前的文件名和路径。如果要禁用这些事件,则将它的
EnableRaisingEvents
属性设置为
false
。
Created
,
Deleted
,
Renamed
三个事件比较容易处理,但
Changed
事件就得当心了,你需要设置它的
NotifyFilter
属性以指示监视那些类型的变化。否则,程序会在文件被修改时淹没在不断发生的事件中(缓存区溢出)。
//
设置相关属性
watcher.Path = appPath;
watcher.Filter ="*.txt";
watcher.IncludeSubdirectories =
true
;
//
添加事件处理函数
watcher.Created +=
new
FileSystemEventHandler(OnChanged);
watcher.Deleted +=
new
FileSystemEventHandler(OnChanged);
watcher.Changed +=
new
FileSystemEventHandler(OnChanged);
watcher.Renamed +=
new
RenamedEventHandler(OnRenamed);
void
OnRenamed(
object
sender, RenamedEventArgs e)
{
string
renamedFormat ="File: {0} 被重命名为 :{1}";
txtChangedInfo.Text =
string
.Format(renamedFormat, e.OldFullPath, e.FullPath);
}
void
OnChanged(
object
sender, FileSystemEventArgs e)
{
//
显示通知信息
txtChangedInfo.Text ="
文件
: "+ e.FullPath +"
发生改变
"+ Environment.NewLine;
txtChangedInfo.Text +="
改变类型
: "+ e.ChangeType.ToString();
}
问题
7
:如何使用独立存储文件;
解决方案:
有时你需要将数据存储在文件中,但对本地硬盘驱动器却没有必要的权限(
FileIOPermission
)。这时要用到
System.IO.IsolatedStorage
命名空间中的类,这些类允许你的程序在特定用户的目录下将数据写入文件而不需要直接访问硬盘驱动器的权限:
//
创建当前用户的独立存储
using
(IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())
{
//
创建一个文件夹
store.CreateDirectory("MyFolder");
//
创建一个独立存储文件
using
(Stream fs =
new
IsolatedStorageFileStream("MyFile.txt", FileMode.Create, store))
{
StreamWriter writer =
new
StreamWriter(fs);
writer.WriteLine("Test Line!");
writer.Flush();
}
Debug.WriteLine("
当前大小:
"+ store.CurrentSize.ToString() + Environment.NewLine);
Debug.WriteLine("
范围:
"+ store.Scope.ToString() + Environment.NewLine);
string
[] files = store.GetFileNames("*.*");
if
(files.Length >0)
{
Debug.WriteLine("
当前文件:
"+ Environment.NewLine);
foreach
(
string
file
in
files)
{
Debug.WriteLine(file + Environment.NewLine);
}
}
}
摘要:
文件操作是程序中非常基础和重要的内容,而路径、文件、目录以及
I/O
都是在进行文件操作时的常见主题,这里想把这些常见的问题作个总结,对于每个问题,尽量提供一些解决方案,即使没有你想要的答案,也希望能提供给你一点有益的思路,如果你有好的建议,恳请能够留言,使这些内容更加完善。
主要内容:
一、路径的相关操作,
如判断路径是否合法,路径类型,路径的特定部分,合并路径,系统文件夹路径等内容;
二、相关通用文件对话框,这些对话框可以帮助我们操作文件系统中的文件和目录;
三、文件、目录、驱动器的操作,如获取它们的基本信息,获取和设置文件和目录的属性,文件的版本信息,
搜索文件和目录,文件判等,复制、移动、删除、重命名文件和目录;
四、读写文件,包括临时文件,随机文件名等;
五、对文件系统的监视;
上一篇介绍了第一、二部分,这一篇介绍一下最重要的第三部分。
三、文件和目录相关操作
文件和目录操作涉及的类主要是:
FileInfo,DirectoryInfo,DriveInfo
,可以认为它们的一个实例对应着一个文件、目录、驱动器。它们的用法类似,一般是将文件、目录或驱动器的路径作为参数传递给相应的构造函数创建一个实例,然后访问它们的属性和方法。
注意下面几点:
FileInfo
类和
DirectoryInfo
类都继承自抽象类
FileSystemInfo
,
FileSystemInfo
类定义了一些通用的属性,如
CreationTime
、
Exists
等。但
DriveInfo
类没有继承
FileSystemInfo
类,所以它也就没有上面提到的那些通用属性了。
FileInfo
类和
DirectoryInfo
类的对象公开的属性值都是第一次查询时获取的值,如果在以此查询之后文件或目录发生了改动,就必须调用它们的
Refresh
方法来更新这些属性。但
DriveInfo
则无需这么做,它的属性每次都会读取文件系统最新的信息。
在创建文件、目录或驱动器的实例时,如果使用了一个不存在的路径,并不会报错,这是你得到一个对象,该对象表示一个并不存在的实体,这意味着它的
Exists
属性(对于
DriveInfo
来说是
IsReady
属性)值为
false
。你仍然可以操作该实体,但如果尝试其它的大多数属性,就会引发相应的
FileNotFoundException
、
DirectoryNotFoundException
或
DriveNotFoundException
异常。
另外,还可以使用
File / Directory
类,这两个类的成员都是静态方法,所以如果只想执行一个操作,那么使用
File/Directory
中的静态方法的效率比使用相应的
FileInfo / DirectoryInfo
中的
实例方法可能更高。所有的
File / Directory
方法都要求当前所操作的文件
/
目录的路径。
注意:
File / Directory
类的静态方法对所有方法都执行安全检查。如果打算多次重用某个对象,可考虑改用
FileInfo / DirectoryInfo
的相应实例方法,因为并不总是需要安全检查。
下面是一些常见的问题:
问题
1
:如何获取指定文件的基本信息;
解决方案:可以使用
FileInfo
类的相关属性:
FileInfo.Exists
:获取指定文件是否存在;
FileInfo.Name
,
FileInfo.Extensioin
:获取文件的名称和扩展名;
FileInfo.FullName
:获取文件的全限定名称(完整路径);
FileInfo.Directory
:获取文件所在目录,返回类型为
DirectoryInfo
;
FileInfo.DirectoryName
:获取文件所在目录的路径(完整路径);
FileInfo.Length
:获取文件的大小(字节数);
FileInfo.IsReadOnly
:获取文件是否只读;
FileInfo.Attributes
:获取或设置指定文件的属性,返回类型为
FileAttributes
枚举,可以是多个值的组合(见问题
2
);
FileInfo.CreationTime
、
FileInfo.LastAccessTime
、
FileInfo.LastWriteTime
:分别用于获取文件的创建时间、访问时间、修改时间;
(更多内容还请参考
MSDN
)
问题
2
:如何获取和设置文件的属性,比如只读、存档、隐藏等;
解决方案:
使用
FileInfo.Attributes
属性可以获取和设置文件的属性,该属性类型为
FileAttributes
枚举,该枚举的每个值表示一种属性,
FileAttributes
枚举具有属性(
Attribute
)
FlagsAttribute
,所以该枚举的值可以进行组合,也就是一个文件可以同时拥有多个属性。下面看看具体的做法:
获取属性,比如判断一个文件是否是只读的:
//
当文件具有其它属性时,这种做法会失败
if
(file.Attributes == FileAttributes.ReadOnly)
{
chkReadonly.Checked =
true
;
}
//
这种写法就不会有问题了,它只检查只读属性
if
((file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
chkReadonly.Checked =
true
;
}
设置属性,比如添加和移除一个文件的只读属性:
if
(chkReadonly.Checked)
{
//
添加只读属性
file.Attributes |= FileAttributes.ReadOnly;
}
else
{
//
移除只读属性
file.Attributes &=~FileAttributes.ReadOnly;
}
问题
3
:如何获取文件的版本信息(比如版本号,版权声明,公司名称等);
解决方案:
使用
FileVersionInfo
类,该类有大量的版本信息相关的属性。通过它的静态方法
GetVersionInfo
获得该类的一个实例,然后就可以访问指定文件的版本信息了,非常方便。如
FileVersion
表示文件版本号,
LegalCopyright
表示指定文件的版权声明,
CompanyName
表示指定文件的公司名称。(更多内容还请参考
MSDN
)
问题
4
:如何判断两个文件的内容是否相同(精确匹配);
解决方案:
使用
System.security.Cryptography.HashAlgorithm
类为每个文件生成一个哈希码,然后比较两个哈希码是否一致。
在比较文件内容的时候可以采用好几种方法。例如,检查文件的某一特定部分是否一致;如果愿意,你甚至可以逐字节读取文件,逐字节进行比较。这两种方法都是可以的,但在某些情况下,还是使用哈希码算法更为方便。
该算法为一个文件生成一个小的(通常约为
20
字节)二进制
”
指纹
”
(
binary fingerprint
)。从统计学角度看,不同的文件不可能生成相同的哈希码。事实上,即使是一个很小的改动(比如,修改了源文件中的一个
bit
),也会有
50
%的几率来改变哈希码中的每一个
bit
。因此,哈希码常常用于数据安全方面。
要生成一个哈希码,你必须首先创建一个
HashAlgorithm
对象,而这通常是调用
HashAlgorithm.Create
方法来完成的;然后调用
HashAlgorithm.ComputeHash
方法,它会返回一个存储哈希码的字节数组。代码如下:
///
///
判断两个文件内容是否一致
///
Public
static bool IsFilesEqual(string fileName1, string fileName2)
{
using
(HashAlgorithm hashAlg = HashAlgorithm.Create())
{
using
(FileStream fs1 =
new
FileStream(fileName1, FileMode.Open), fs2 =
new
FileStream(fileName2, FileMode.Open))
{
byte
[] hashBytes1 = hashAlg.ComputeHash(fs1);
byte
[] hashBytes2 = hashAlg.ComputeHash(fs2);
//
比较哈希码
return
(BitConverter.ToString(hashBytes1) == BitConverter.ToString(hashBytes2));
}
}
}
问题
5
:如何获取指定目录的基本信息;
解决方案:可以使用
DirectoryInfo
类的相关属性和方法:
DirectoryInfo.Exists
:获取指定目录是否存在;
DirectoryInfo.Name
:获取目录的名称;
DirectoryInfo.FullName
:获取目录的全限定名称(完整路径);
DirectoryInfo.Attributes
:获取或设置指定目录的属性,返回类型为
FileAttributes
枚举,可以是多个值的组合;
DirectoryInfo.CreationTime
、
FileInfo.LastAccessTime
、
FileInfo.LastWriteTime
:分别用于获取目录的创建时间、访问时间、修改时间;
DirectoryInfo.Parent
:获取目录的上级目录,返回类型为
DirectoryInfo
;
DirectoryInfo.Root
:获取目录的根目录,返回类型为
DirectoryInfo
;
问题
6
:如何获取指定目录包含的文件和子目录;
解决方案:
DirectoryInfo.GetFiles()
:获取目录中(不包含子目录)的文件,返回类型为
FileInfo[]
,支持通配符查找;
DirectoryInfo.GetDirectories()
:获取目录(不包含子目录)的子目录,
返回类型为
DirectoryInfo[]
,支持通配符查找;
DirectoryInfo. GetFileSystemInfos()
:获取指定目录下(不包含子目录)的文件和子目录,
返回类型为
FileSystemInfo[]
,支持通配符查找;
问题
7
:如何获得指定目录的大小;
解决方案:
检查目录内的所有文件,利用
FileInfo.Length
属性获取每个文件的大小,然后进行合计,然后使用递归算法处理所有的子目录的文件,参考下面代码:
///
///
计算一个目录的大小
///
///
///
///
Private
long CalculateDirSize(DirectoryInfo di, bool includeSubDir)
{
long
totalSize =0;
//
检查所有(直接)包含的文件
FileInfo[] files = di.GetFiles();
foreach
(FileInfo file
in
files)
{
totalSize += file.Length;
}
//
检查所有子目录,如果
includeSubDir
参数为
true
if
(includeSubDir)
{
DirectoryInfo[] dirs = di.GetDirectories();
foreach
(DirectoryInfo dir
in
dirs)
{
totalSize += CalculateDirSize(dir, includeSubDir);
}
}
return
totalSize;
}
问题
8
:如何使用通配符搜索指定目录内的所有文件;
解决方案:
使用
DirectoryInfo.GetFiles
方法的重载版本,它可以接受一个过滤表达式,返回
FileInfo
数组,另外它的参数还可以指定是否对子目录进行查找。如:
dir.GetFiles("*.txt"
,
SearchOption.AllDirectories);
问题
9
:如何复制、移动、重命名、删除文件和目录;
解决方案:使用
FileInfo
和
DirectoryInfo
类。
下面是
FileInfo
类的相关方法:
FileInfo.CopyTo
:将现有文件复制到新文件,其重载版本还允许覆盖已存在文件;
FileInfo.MoveTo
:将指定文件移到新位置,并提供指定新文件名的选项,所以可以用来重命名文件(而不改变位置);
FileInfo.Delete
:永久删除文件,如果文件不存在,则不执行任何操作;
FileInfo.Replace
:使用当前
FileInfo
对象对应文件的内容替换目标文件,而且指定另一个文件名作为被替换文件的备份,微软考虑实在周到。
下面是
DirectoryInfo
类的相关方法:
DirectoryInfo.Create
:创建指定目录,如果指定路径中有多级目录不存在,该方法会一一创建;
DirectoryInfo.CreateSubdirectory
:创建当前对象对应的目录的子目录;
DirectoryInfo.MoveTo
:将目录(及其包含的内容)移动至一个新的目录,也可用来重命名目录;
DirectoryInfo.Delete
:删除目录(如果它存在的话)。如果要删除一个包含子目录的目录,要使用它的重载版本,以指定递归删除。
注意到了没有?
DirectoryInfo
类少了一个
CopyTo
方法,不过我们可以通过递归来实现这个功能:
///
///
复制目录到目标目录
///
///
///
Public
static void CopyDirectory(DirectoryInfo source, DirectoryInfo destination)
{
//
如果两个目录相同,则无须复制
if
(destination.FullName.Equals(source.FullName))
{
return
;
}
//
如果目标目录不存在,创建它
if
(!destination.Exists)
{
destination.Create();
}
//
复制所有文件
FileInfo[] files = source.GetFiles();
foreach
(FileInfo file
in
files)
{
//
将文件复制到目标目录
file.CopyTo(Path.Combine(destination.FullName, file.Name),
true
);
}
//
处理子目录
DirectoryInfo[] dirs = source.GetDirectories();
foreach
(DirectoryInfo dir
in
dirs)
{
string
destinationDir = Path.Combine(destination.FullName, dir.Name);
//
递归处理子目录
CopyDirectory(dir,
new
DirectoryInfo(destinationDir));
}
}
问题
10
:如何获得计算机的所有逻辑驱动器;
解决方案:使用
DriveInfo
类(需要
.NET 2.0
)
DriveInfo.GetDrives()
:获得计算机的所有逻辑驱动器,返回类型为
DriveInfo[]
;
问题
11
:如何获取指定驱动器的信息;
解决方案:
DriveInfo.Name
:获取驱动器的名称(如
C:/
);
DriveInfo.DriveType
:获取驱动器的类型(如
Fixed
,
CDRom
,
Removable
,
Network
等);
DriveInfo.DriveFormat
:获取驱动器的格式(如
NTFS
,
FAT32
,
CDFS
,
UDF
等);
DriveInfo.IsReady
:获取驱动器是否已准备好,比如
CD
是否已放入
CD
驱动器,如果驱动器没有准备好,访问其信息会引发
IOException
类型异常;
DriveInfo.AvailableFreeSpace
:获取驱动器的可用空间;
DriveInfo.TotalFreeSpace
:获取驱动器总的可用空间,它与
AvailableFreeSpace
的不同在于
AvailableFreeSpace
会磁盘配额的设置;
DriveInfo.TotalSize
:获取驱动器总的空间;
DriveInfo.RootDirectory
:获得驱动器的根目录(
DirectoryInfo
类型);
至此,我们已经了解了文件和目录相关的一些基本操作。但还不清楚如何去读写文件的内容,下一篇中会详细了解这方面的操作。
路径,文件,目录,I/O常见操作汇总(三)
主要内容:
一、路径的相关操作,如判断路径是否合法,路径类型,路径的特定部分,合并路径,系统文件夹路径等内容;
二、相关通用文件对话框,这些对话框可以帮助我们操作文件系统中的文件和目录;
三、文件和目录操作,如复制、移动、删除、重命名,文件的版本信息,文件判等、搜索,读写文件等;
四、读写文件,对文件系统的监视;
五、其它,如临时文件,随机文件名等;
文件读写相关类介绍:
文件读写操作涉及的类主要是:
MarshalByRefObject
类:允许在支持远程处理的应用程序中跨应用程序域边界访问对象;
BinaryReader
类:用特定的编码将基元数据类型读作二进制值。
BinaryWriter
类:
以二进制形式将基元类型写入流,并支持用特定的编码写入字符串。
Stream
类:
提供字节序列的一般视图。
FileStream
类:公开以文件为主的
Stream
,既支持同步读写操作,也支持异步读写操作。
MemoryStream
类:创建其支持存储区为内存的流。
BufferedStream
类:给另一流上的读写操作添加一个缓冲层。
TextReader
类:表示可读取连续字符系列的阅读器。
TextWriter
类:表示可以编写一个有序字符系列的编写器。
StreamReader
类:实现一个
TextReader
,使其以一种特定的编码从字节流中读取字符。
StreamWriter
类:实现一个
TextWriter
,使其以一种特定的编码向流中写入字符。
StringReader
类:实现从字符串进行读取的
TextReader
。
StringWriter
类:实现一个用于将信息写入字符串的
TextWriter
。该信息存储在基础
StringBuilder
中。
在使用它们之前最好能了解它们的继承关系,有助于作出最合适的选择:
另外还要注意一下
FileInfo
和
File
类的一些方法,如
Create
,
CreateText
,
Open
等,有时也会带来方便。
这些类的内容比较繁多,更多内容还请参考
MSDN
。
下面是一些常见的问题及其解决方案:
问题
1
:如何读写文本文件(并考虑不同的编码类型);
解决方案:
创建一个
FileStream
对象用以引用该文件。要写入文件,将
FileStream
对象封装在
StreamWriter
对象中,使用其重载了的
Write
方法;要读取文件,将
FileStream
对象封装在
StreamReader
对象中,使用其
Read
或
ReadLine
方法;
.NET Framework
允许通过
StreamWriter
和
StreamReader
类操作任何流来读写文本文件。当使用
StreamWriter
类写入数据时,调用它的
Write
方法,该方法在重载后可以支持所有常见的
C#
数据类型,包括字符串、字符、整数、浮点数以及十进制数等。但
Write
方法总会将的得到的数据转换为文本,如果希望将这些文本转换回原来的数据类型,应使用
WriteLine
方法,以确保每个值都处于单独的一行上。
字符串的表现形式取决于你使用的编码,最常见的编码类型包括下面几种:
ASCII
,
UTF-16
,
UTF-7
,
UTF-8
。
.NET Framework
在
System.Text
命名空间中为每种编码类型提供了一个类。在使用
StreamWriter
和
StreamReader
类时,可以指定需要的编码类型,或者使用默认的
UTF-8
。
而在读取文本文件时,则要使用
StreamReader
类的
Read
或
ReadLine
方法。
Read
方法读取单个字符或者指定个数的字符,返回类型为字符或字符数组;
ReadLine
方法则返回包含整行内容的字符串;
ReadToEnd
方法从当前位置读取至流的结尾。
(更多内容还请参考
MSDN
)
写入文本文件的示例:
using
(FileStream fs =
new
FileStream(fileName, FileMode.Create))
{
//
创建一个
StreamWriter
对象,使用
UTF-8
编码格式
using
(StreamWriter writer =
new
StreamWriter(fs, Encoding.UTF8))
{
//
分别写入十进制数,字符串和字符类型的数据
writer.WriteLine(123.45M);
writer.WriteLine("String Data");
writer.WriteLine('A');
}
}
读取文本文件的示例:
//
以只读模式打开一个文本文件
using
(FileStream fs =
new
FileStream(fileName, FileMode.Open))
{
using
(StreamReader reader =
new
StreamReader(fs, Encoding.UTF8))
{
string
text =
string
.Empty;
while
(!reader.EndOfStream)
{
text = reader.ReadLine();
txtMessage.Text += text + Environment.NewLine;
}
}
}
问题
2
:如何读写二进制文件(使用强数据类型);
解决方案:
创建一个
FileStream
对象用以引用该文件。要写入文件,将
FileStream
对象封装在
BinaryWriter
对象中,使用其重载了的
Write
方法;要读取文件,将
FileStream
对象封装在
BinaryReader
对象中,使用相应数据类型的
Read
方法。
.NET Framework
允许通过
BinaryWriter
和
BinaryReader
类操作任何流来读写二进制数据。当使用
BinaryWriter
类写入数据时,调用它的
Write
方法,该方法在重载后可以支持所有常见的
C#
数据类型,包括字符串、字符、整数、浮点数以及十进制数等,然后数据会被编码为一系列字节写入文件,也可以配置该过程中的编码类型。
在使用二进制文件时,一定要特别注意其中的数据类型。当你读取数据时,一定要使用
BinaryReader
类的某种强类型的
Read
方法。例如,要读取字符串,要使用
ReadString
方法。(
BinaryWriter
在写入二进制文件时总会记录字符串的长度以避免任何可能的错误)
写入文件的示例:
using
(FileStream fs =
new
FileStream(fileName, FileMode.Create))
{
using
(BinaryWriter writer =
new
BinaryWriter(fs))
{
//
写入十进制数,字符串和字符
writer.Write(234.56M);
writer.Write("String");
writer.Write('!');
}
}
读取文件的示例:
//
以只读模式打开一个二进制文件
using
(FileStream fs =
new
FileStream(fileName, FileMode.Open))
{
using
(StreamReader sr =
new
StreamReader(fs))
{
MessageBox.Show("
全部数据:
"+ sr.ReadToEnd());
fs.Position =0;
using
(BinaryReader reader =
new
BinaryReader(fs))
{
//
选用合适的数据类型读取数据
string
message = reader.ReadDecimal().ToString() + Environment.NewLine;
message += reader.ReadString() + Environment.NewLine;
message += reader.ReadChar().ToString();
MessageBox.Show(message);
}
}
}
问题
3
:如何异步读取文件;
解决方案:
有时你需要读取一个文件但又不希望影响程序的执行。常见的情况是读取一个存储在网络驱动器上的文件。
FileStream
提供了对异步操作的基本支持,即它的
BeginRead
和
EndRead
方法。使用这些方法,可以在
.NET Framework
线程池提供的线程中读取一个数据块,而无须直接与
System.Threading
命名空间中的线程类打交道。
采用异步方式读取文件时,可以选择每次读取数据的大小。根据情况的不同,你可能会每次读取很小的数据(比如,你要将数据逐块拷贝至另一个文件),也可能是一个相对较大的数据(比如,在程序逻辑开始之前需要一定数量的数据)。在调用
BeginRead
时指定要读取数据块的大小,同时传入一个缓冲区(
buffer
)以存放数据。因为
BeginRead
和
EndRead
需要访问很多相同的信息,如
FileStream
,
buffer
,数据块大小等,因此将这些内容封装一个单独的类当中是一个好主意。
下面这个类就是一个简单的示例。
AsyncProcessor
类提供了
StartProcess
方法,调用它开始读取,每次读取操作结束,
OnCompletedRead
回调函数会被触发,此时可以处理数据,如果还有剩余数据,则开始一个新的读取操作。默认情况下,
AsyncProcessor
类每次读取
2KB
数据。
class
AsyncProcessor
{
private
Stream inputStream;
//
每次读取块的大小
Private
int bufferSize =2048;
Public
int BufferSize
{
get
{
return
bufferSize; }
set
{ bufferSize = value; }
}
//
容纳接收数据的缓存
Private
byte[] buffer;
public
AsyncProcessor(
string
fileName)
{
buffer =
new
byte
[bufferSize];
//
打开文件,指定参数为
true
以提供对异步操作的支持
inputStream =
new
FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize,
true
);
}
Public
void StartProcess()
{
//
开始异步读取文件,填充缓存区
inputStream.BeginRead(buffer, 0, buffer.Length, OnCompletedRead,
null
);
}
Private
void OnCompletedRead(IAsyncResult asyncResult)
{
//
已经异步读取一个
块
,接收数据
int
bytesRead = inputStream.EndRead(asyncResult);
//
如果没有读取任何字节,则流已达文件结尾
if
(bytesRead >0)
{
//
暂停以模拟对数据块的处理
Debug.WriteLine("
异步线程:已读取一块
");
Thread.Sleep(TimeSpan.FromMilliseconds(20));
//
开始读取下一块
inputStream.BeginRead(buffer, 0, buffer.Length, OnCompletedRead,
null
);
}
else
{
//
结束操作
Debug.WriteLine("
异步线程:读取文件结束
");
inputStream.Close();
}
}
}
使用该类时可以这么写:
//
开始在另一线程中异步读取文件
AsyncProcessor asyncIO =
new
AsyncProcessor("test.txt");
asyncIO.StartProcess();
//
在主程序中,做其它事情,这里简单地循环
10
秒
DateTime startTime = DateTime.Now;
while
(DateTime.Now.Subtract(startTime).TotalSeconds <
10)
{
Debug.WriteLine("
主程序:正在进行
");
//
暂停线程以模拟耗时的操作
Thread.Sleep(TimeSpan.FromMilliseconds(100));
}
Debug.WriteLine("
主程序:已完成
");
问题
4
:如何创建临时文件;
解决方案:
有时需要在特定用户的临时目录下创建一个临时文件,这要求该文件具有唯一的名称,避免与其它程序生成的临时文件相冲突。我们会有多种选择。最简单的是,在程序所在目录内使用
GUID
或时间戳加上随机值作为文件名称。但
Path
类提供的方法还是可以为你节省工作量,这就是它的静态
GetTempFileName
方法,它在当前用户的临时目录下创建一个临时文件(文件名称一定是唯一的),临时目录通常类似于这样:
C:/Documents and Settings/[username]/Local Settings/Temp
。
string
tempFile = Path.GetTempFileName();
using
(FileStream fs =
new
FileStream(tempFile, FileMode.Open))
{
using
(BinaryWriter writer =
new
BinaryWriter(fs))
{
//
写入数据
writer.Write("
临时文件信息
");
}
}
// Do something
//
最后删除临时文件
File.Delete(tempFile);
问题
5
:如何获得随机文件名;
解决方案:
使用
Path.GetRandomFileName
方法,它与
GetTempFileName
方法的不同之处在于它仅仅返回一个字符串但不会创建文件。
问题
6
:监视文件系统的变化;
解决方案:
如果指定路径内的文件发生改变(比如文件被修改或创建),你希望能对此作出反应。
如果程序与其它多个程序或业务处理相关,常常需要创建一个程序,并且只有文件系统发生变化时它才处于活动状态。你可以创建一个这样的程序,让它定期区检测指定目录,此时会发现有件事情让你苦恼:检测得越频繁,就会浪费越多的系统资源;而检测得越少,那么检测到变化的时间就会越长。
这时可以使用
FileSystemWatcher
组件,指定要进行监视的目录或文件,并处理其
Created
,
Deleted
,
Renamed
,
Changed
事件。
要使用
FileSystemWatcher
组件,首先要创建它的一个实例,然后设置下列属性:
Path
:指定要监视的目录;
Filter
:指定要监视的文件类型,如
”*.txt”
;
NotifyFilter
:指定要监视的变化类型;
FileSystemWatcher
会引发四个关键的事件:
Created
,
Deleted
,
Renamed
,
Changed
。这些事件都在其
FileSystemEventArgs
参数中提供了相关文件的信息:如文件名,路径,改变类型,
Renamed
事件中还可以了解到改变前的文件名和路径。如果要禁用这些事件,则将它的
EnableRaisingEvents
属性设置为
false
。
Created
,
Deleted
,
Renamed
三个事件比较容易处理,但
Changed
事件就得当心了,你需要设置它的
NotifyFilter
属性以指示监视那些类型的变化。否则,程序会在文件被修改时淹没在不断发生的事件中(缓存区溢出)。
//
设置相关属性
watcher.Path = appPath;
watcher.Filter ="*.txt";
watcher.IncludeSubdirectories =
true
;
//
添加事件处理函数
watcher.Created +=
new
FileSystemEventHandler(OnChanged);
watcher.Deleted +=
new
FileSystemEventHandler(OnChanged);
watcher.Changed +=
new
FileSystemEventHandler(OnChanged);
watcher.Renamed +=
new
RenamedEventHandler(OnRenamed);
void
OnRenamed(
object
sender, RenamedEventArgs e)
{
string
renamedFormat ="File: {0} 被重命名为 :{1}";
txtChangedInfo.Text =
string
.Format(renamedFormat, e.OldFullPath, e.FullPath);
}
void
OnChanged(
object
sender, FileSystemEventArgs e)
{
//
显示通知信息
txtChangedInfo.Text ="
文件
: "+ e.FullPath +"
发生改变
"+ Environment.NewLine;
txtChangedInfo.Text +="
改变类型
: "+ e.ChangeType.ToString();
}
问题
7
:如何使用独立存储文件;
解决方案:
有时你需要将数据存储在文件中,但对本地硬盘驱动器却没有必要的权限(
FileIOPermission
)。这时要用到
System.IO.IsolatedStorage
命名空间中的类,这些类允许你的程序在特定用户的目录下将数据写入文件而不需要直接访问硬盘驱动器的权限:
//
创建当前用户的独立存储
using
(IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForAssembly())
{
//
创建一个文件夹
store.CreateDirectory("MyFolder");
//
创建一个独立存储文件
using
(Stream fs =
new
IsolatedStorageFileStream("MyFile.txt", FileMode.Create, store))
{
StreamWriter writer =
new
StreamWriter(fs);
writer.WriteLine("Test Line!");
writer.Flush();
}
Debug.WriteLine("
当前大小:
"+ store.CurrentSize.ToString() + Environment.NewLine);
Debug.WriteLine("
范围:
"+ store.Scope.ToString() + Environment.NewLine);
string
[] files = store.GetFileNames("*.*");
if
(files.Length >0)
{
Debug.WriteLine("
当前文件:
"+ Environment.NewLine);
foreach
(
string
file
in
files)
{
Debug.WriteLine(file + Environment.NewLine);
}
}
}