asp.net C# 常见问题 路径,文件,目录,IO汇总

作者:袖梨 2022-06-25

asp教程.net c# 常见问题 路径,文件,目录,io汇总
主要内容:
一、路径的相关操作,如判定路径是否合法,路径类型,路径的特定部分,合并路径,系统文件夹路径等内容;
二、相关通用文件对话框,这些对话框可以帮助我们操作文件系统中的文件和目录;
三、文件、目录、驱动器的操作,如获取它们的基本信息,获取和设置文件和目录的属性,文件的版本信息,
搜索文件和目录,文件判等,复制、移动、删除、重命名文件和目录;
四、读写文件,包括临时文件,随机文件名等;
五、对文件系统的监视;
这一篇就先写一下前两部分。

一、路径相关操作
问题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)、空()以及从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;
dialogresultresult=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;
dialogresultdr=dlgopenfile.showdialog();
if(dr==dialogresult.ok)
{
     stringfilename=dlgopenfile.filename;
}
 

3、保存文件对话框(savefiledialog类)
用户可以通过该对话框保存一个文件

 

主要属性:
大部分与打开文件对话框类似,此处略过,下面几个值得注重:
createprompt:该值指示假如用户指定不存在的文件,是否提示用户答应创建该文件;
overwriteprompt:该值指示假如用户指定的文件名已存在,对话框是否显示警告;
主要方法:
openfile:打开用户选定的具有读/写权限的文件;
showdialog:打开该模式对话框;
示例代码:
dlgsavefile.title="打开目标文件";
dlgsavefile.initialdirectory=@"c:inetpub";
dlgsavefile.filter="文本文件(*.txt)|*.txt|所有文件(*.*)|*.*";
dlgsavefile.filterindex=2;
dialogresultdr=dlgsavefile.showdialog();
if(dr==dialogresult.ok)
{
     stringfilename=dlgsavefile.filename;
}
 


至此,我们操作的都只是路径,要知道,这些路径仅仅是字符串,还没有涉及到文件系统中的真实文件。

三、文件和目录相关操作
文件和目录操作涉及的类主要是: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字节)二进制”指纹”(binaryfingerprint)。从统计学角度看,不同的文件不可能生成相同的哈希码。事实上,即使是一个很小的改动(比如,修改了源文件中的一个bit),也会有50%的几率来改变哈希码中的每一个bit。因此,哈希码经常用于数据安全方面。
要生成一个哈希码,你必须首先创建一个hashalgorithm对象,而这通常是调用hashalgorithm.create方法来完成的;然后调用hashalgorithm.computehash方法,它会返回一个存储哈希码的字节数组。代码如下:
///


///判定两个文件内容是否一致
///

publicstaticboolisfilesequal(stringfilename1,stringfilename2)
{
     using(hashalgorithm hashalg= hashalgorithm.create())
     {
          using(filestreamfs1=newfilestream(filename1,filemode.open),fs2=newfilestream(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属性获取每个文件的大小,然后进行合计,然后使用递归算法处理所有的子目录的文件,参考下面代码:

///


///计算一个目录的大小
///

///指定目录
///是否包含子目录
///
privatelongcalculatedirsize(directoryinfo di, bool includesubdir)
{
     longtotalsize=0;

     //检查所有(直接)包含的文件
     fileinfo[]files=di.getfiles();
     foreach(fileinfo file in files)
     {
          totalsize =file.length;
     }

//检查所有子目录,假如includesubdir参数为true
if(includesubdir)
{
     directoryinfo[]dirs=di.getdirectories();
     foreach(directoryinfodirindirs)
     {
          totalsize =calculatedirsize(dir,includesubdir);
     }
}
     returntotalsize;
}
 

问题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 destination dir=path.combine(destination.fullname,dir.name);

          //递归处理子目录
          copydirectory(dir,newdirectoryinfo(destinationdir));
     }
}

 

 

问题10:如何获得计算机的所有逻辑驱动器;
解决方案:使用driveinfo类(需要.net2.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类型);

至此,我们已经了解了文件和目录相关的一些基本操作。

文件读写相关类介绍:
文件读写操作涉及的类主要是:
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方法;
.netframework答应通过streamwriter和streamreader类操作任何流来读写文本文件。当使用streamwriter类写入数据时,调用它的write方法,该方法在重载后可以支持所有常见的c#数据类型,包括字符串、字符、整数、浮点数以及十进制数等。但write方法总会将的得到的数据转换为文本,假如希望将这些文本转换回原来的数据类型,应使用writeline方法,以确保每个值都处于单独的一行上。
字符串的表现形式取决于你使用的编码,最常见的编码类型包括下面几种:ascii,utf-16,utf-7,utf-8。
.netframework在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("stringdata");
          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方法。
.netframework答应通过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方法。使用这些方法,可以在.netframework线程池提供的线程中读取一个数据块,而无须直接与system.threading命名空间中的线程类打交道。
采用异步方式读取文件时,可以选择每次读取数据的大小。根据情况的不同,你可能会每次读取很小的数据(比如,你要将数据逐块拷贝至另一个文件),也可能是一个相对较大的数据(比如,在程序逻辑开始之前需要一定数量的数据)。在调用beginread时指定要读取数据块的大小,同时传入一个缓冲区(buffer)以存放数据。因为beginread和endread需要访问很多相同的信息,如filestream,buffer,数据块大小等,因此将这些内容封装一个单独的类当中是一个好主意。
下面这个类就是一个简单的示例。asyncprocessor类提供了startprocess方法,调用它开始读取,每次读取操作结束,oncompletedread回调函数会被触发,此时可以处理数据,假如还有剩余数据,则开始一个新的读取操作。默认情况下,asyncprocessor类每次读取2kb数据。

classasyncprocessor
{
     private stream inputstream;

     //每次读取块的大小
     private int buffersize=2048;

     public int buffersize
     {
          get{returnbuffersize;}
          set{buffersize=value;}
     }

     //容纳接收数据的缓存
     privatebyte[]buffer;

     public asyncprocessor(string filename)
     {
          buffer=newbyte[buffersize];

          //打开文件,指定参数为true以提供对异步操作的支持
          inputstream=newfilestream(filename,filemode.open,fileaccess.read,fileshare.read,buffersize,true);
     }

     publicvoidstartprocess()
     {
          //开始异步读取文件,填充缓存区
          inputstream.beginread(buffer,0,buffer.length,oncompletedread,null);
     }

     privatevoidoncompletedread(iasyncresultasyncresult)
     {
          //已经异步读取一个块,接收数据
          intbytesread=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:documentsandsettings[username]localsettingstemp。
string tempfile = path.gettempfilename();

using(filestream fs = new filestream(tempfile,filemode.open))
{
     using(binarywriter writer = new binarywriter(fs))
     {
          //写入数据
          writer.write("临时文件信息");
     }
}

//dosomething

//最后删除临时文件
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(streamfs=newisolatedstoragefilestream("myfile.txt",filemode.create,store))
     {
          streamwriterwriter=newstreamwriter(fs);
          writer.writeline("testline!");
          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(stringfileinfiles)
          {
               debug.writeline(file environment.newline);
          }
     }
}

相关文章

精彩推荐