文件和流(文件系统)

       大多数 Web 应用程序依赖数据库来存储信息。在多用户场景中,数据库是无与伦比的。但还是会有不可避免的遇到访问存储在其他地方(如文件系统)的数据的问题。常见的示例有:读取其他程序产生的信息、为测试而编写的临时日志等。

 

使用文件系统

       最基本的文件访问包括获取现有文件和目录的信息,以及执行典型的文件系统操作,如复制文件和创建目录。这些任务并没有设计真正的打开或写文件。

       .NET 提供了几个基类(System.IO 命名空间中)用于获取文件系统信息:

  • Directory 和 File :这两个类提供一组静态方法,可以通过它们获取任意服务器上可见的文件和目录的信息。
  • DirectoryInfo、FileInfo、DriveInfo :这些类用相似的实例方法获取同样的信息。

       这两组类提供相似的方法和属性。一般而言,Directory 和 File 类更适合处理一次性任务。DirectoryInfo 和 FileInfo 更适合获取若干信息,这样不必再每次调用方法时都提供文件或目录的信息。同时,它们还更快一些,因为 DirectoryInfo 和 FileInfo 类只执行一次安全检查(创建对象的实例时),而 Directory 和 File 在每次调用方法时都需要执行安全检查。

 

Directory 类和 File 类

       它们提供了很多有用的方法,每个方法都接受相同的参数:用于识别执行操作的文件和目录的完全限定路径名

Directory 方法:

CreateDirectory() 创建一个或一组目录(创建不存在的目录中的子目录时)。
Delete() 删除对应的空目录。如果要删除该目录及其所有内容(子目录和文件),第二个可选参数设为 True
Exists() 返回 bool 值表示指定的目录是否存在
GetCreationTime()
GetLastAccessTime()
GetLastWriteTime()
返回一个 DateTime 对象,代表创建、访问、写入目录的时间。每个Get方法都有一个对应的Set方法,这里没有列出。
GetDirectories()
GetFiles()
返回一个字符串数组,数组元素代表指定目录中的子目录或文件。第二个可选参数支持搜索表达式,如 (ASP*.*)
GetLogicaDrives() 返回一个字符串数组,数组元素代表当前计算机上定义的一个驱动器,如 c:\
GetParent() 返回指定目录的父目录
GetCurrentDirectory()
SetCurrentDirectory()
设置和获取当前目录
Move() 接收两个参数:源路径和目标路径。目录及其内容可以移动到任意路径,但必须在同一个磁盘
GetAccessControl()
SetAccessControl()
返回或设置一个 System.Secutiry.AccessControl.DirectorySecurity 对象。可以使用这个对象检查 Windows 访问控制表(ACL),它们应用于这个目录且可以通过编程来修改。

 

File 方法:

Copy() 接收两个参数:源文件和目标文件的完整文件名。如果允许覆写,第三个可选参数设为 true
Delete() 删除指定的文件,如果找不到文件也不会抛出异常。
Exists() 文件是否存在
GetAttributes()
SetAttributes()
获取或设置一个可以包含 FillAttribute 枚举值的任意组合的枚举值
GetCreationTime()
GetLastAccessTime()
GetLastWriteTime()
返回一个 DateTime 对象,代表创建、访问、写入文件的时间。每个Get方法都有一个对应的Set方法,这里没有列出。
Move() 接收两个参数:源文件和目标文件的完整文件名。可以跨磁盘移动文件甚至同时重命名文件
Create()
CreateText()
创建指定的文件并返回一个可用于写的 FileStream 对象。CreateText() 执行同样的任务但是返回一个封装了流的 StreamWriter 对象。
Open()
OpenText()
OpenRead()
OpenWrite()

打开一个文件(假设其存在)。OpenText() 和 OpenRead() 以只读方式打开文件,返回一个 FileStream 或 StreamReader 。OpenWrite() 以只写方式打开文件,返回 FileStream
ReadAllText()
ReadAllLines()
ReadAllBytes()
读取整个文件。分别以单一字符串、字符串数组、字节数组的形式返回内容。仅在小文件才使用这些方法,对于大型文件,使用流每次读取一块以节省内存使用。
WriteAllText()
WriteAllLines()
WriteAllBytes()

将单一字符串、字符串数组、字节数组一次写入完整文件内容。可以覆盖已存在的文件。
GetAccessControl()
SetAccessControl()
返回或设置一个 System.Secutiry.AccessControl.FileSecurity 对象。可以使用这个对象检查 Windows 访问控制表(ACL),它们应用于这个目录且可以通过编程来修改。

提示:File 类缺乏的唯一功能是获取给定文件的大小(可由 FileInfo 类提供)。

string directoryName = @"d:\AnswerQuestion";
ListBox1.DataSource = Directory.GetFiles(directoryName);
ListBox1.DataBind();
image

 

 

DirectoryInfo 类和 FileInfo 类

       DirectoryInfo 和 FileInfo 类镜像 Directory 和 File 类的功能。此外,它们还可以轻易的遍历文件和目录的关系。

       Directory 类和 File 类只提供了方法,而 DirectoryInfo 类和 FileInfo 类同时还提供了属性。DirectoryInfo 类和 FileInfo 类的另一个好处是它们共享一组属性和方法,因为它们都继承自 FileSystemInfo 基类。

DirectoryInfo 和 FileInfo 的共有成员: 

Attributes 获取和设置 FileAttributes 枚举值,从而设置文件特性
CreationTime()
LastAccessTime()
LastWriteTime()

允许使用 DateTime 对象获取和设置创建、最后访问和最后写入的时间
Exists 按照文件和目录是否存在返回 true 或 false
FullName
Name
Extension

返回一个字符串,分别代表完全限定名、目录或文件名(带有扩展名)、扩展名
Delete() 如果文件或目录存在,则删除。删除目录时必须为空,否则指定一个为 true 的可选参数
Refresh() 更新对象使其与文件系统发生的任意变化同步。
Create() 创建指定的文件或目录
MoveTo() 复制目录及其内容或者文件。对于 DirectoryInfo 需要指定新路径,对于 FileInfo 需要指定路径和文件名

DirectoryInfo 特有成员:

Parent 和 Root 返回父目录或根目录
CreateSubdirectory() DirectoryInfo 对象代表的目录中创建一个目录,返回一个代表子目录的 DirectoryInfo 对象
GetDirectories() 返回一个 DirectoryInfo 数组,代表当前目录的所有子目录
GetFiles() 返回代表当前目录中所有文件的 FileInfo 对象的数组

 

FileInfo 特有成员:

Directory 返回一个代表父目录的 DirectoryInfo 对象
DirectoryName 返回父目录名称的字符串
Length 返回长整形的文件字节数
CopyTo() 复制文件到指定的新路径和文件名。返回一个代表新文件(复制的)的 FileInfo 对象
Create()
CreateText()
创建指定的文件,返回一个可用来写文件的 FileStream 或 封装了流的 StreamWriter 对象
Open()
OpenText()
OpenReader()
OpenWrite()

打开一个文件(假设存在)。OpenRead 和 OpenText 以只读方式打开文件,返回一个 FileStream 或 StreamReader 。OpenWrite 以只写方式打开文件,返回一个 FileStream
// new 对象时不一定要对应真实的物理文件或目录
DirectoryInfo myDerectory = new DirectoryInfo(@"d:\AnswerQuestion");
FileInfo myFile = new FileInfo(@"d:\AnswerQuestion\test.txt");
 
// 如果不确定,可以这样检查
if (!myDerectory.Exists)
{
    myDerectory.Create();
}
if (!myFile.Exists)
{
    // Create() 返回一个可写的文件流
    FileStream stream = myFile.Create();
    stream.Close();
}

       DirectoryInfo 和 FileInfo 对象在你第一次查询某个属性时获取来自文件系统的信息,在后续的使用中不再检查新的信息。如果此时文件发生了变化会导致不一致性。如果你知道或者怀疑数据有不一致的可能,可以调用 ReFresh()方法获取最新的信息。

       DirectoryInfo 没有提供任何获取目录大小的属性。不过你可以累加所有文件的 File.Lengh 属性:

private static long CalculateDirectorySize(DirectoryInfo directory, bool includeSubdirectories)
{
    long totalSize = 0;
    FileInfo[] files = directory.GetFiles();
    foreach (FileInfo file in files)
    {
        totalSize += file.Length;
    }
 
    if (includeSubdirectories)
    {
        DirectoryInfo[] dirs = directory.GetDirectories();
        foreach (DirectoryInfo dir in dirs)
        {
            totalSize += CalculateDirectorySize(dir, true);
        }
    }
    return totalSize;    
}

 

DriveInfo

       关于剩余空间的信息,要借助 DriveInfo 类。DriveInfo 允许你获得计算机驱动器的信息。没什么信息会让你感兴趣,除了获取磁盘已经使用的空间大小和剩余空间的大小。

TotalSize 驱动器的总字节数
TotalFreeSpace 磁盘剩余字节数
AvailableFreeSpace 磁盘可用空间的字节数
DriveFormat 驱动器所使用的文件系统名称(NTFS 或 FAT32)
DriveType 返回一个 DriveType 枚举值,表明驱动器是固定的、网络的、CD-ROM等等或者返回 Unknown
IsReady 返回驱动器是否已经准备好用于读或写
Name 返回驱动器符号(如 C: E: )
VolumeLabel 返回或设置驱动器卷标
RootDirectory 返回驱动器根目录的 DirectoryInfo 对象
GetDrives() 获取一个 DriveInfo 数组,代表当前计算机的所有逻辑分区

 

使用 Attributes

       FileInfo 和 DirectoryInfo 类的 Attributes 属性代表文件或目录的文件系统特性,所有它包含一组 FileAttributes 枚举值。

FileAttributes 枚举值:

Archive 项已经归档。应用程序可以基于这个特性标志备份或删除的文件。
Compressed 项被压缩
Device 当前不使用,为以后保留的枚举关键字
Directory 项是一个目录
Encrypted 项被加密
Hidden 项是隐藏的(但仍然可以在 Windows 资源管理器里看到它)
Normal 项是普通的,没有任何特性设置。这个特性仅在单独使用时有效
NotContentIndexed 项不会按照操作系统的内容索引服务来服务
Offline 文件不在线且当前不可用
Readonly 项是只读的
ReparsePoint 文件包含再解析点,它是与NTFS文件系统中的文件或目录关联的一块用户定义的数据
SparseFile 文件是稀疏文件。稀疏文件通常是由大量零组成的大型文件。这个项仅被 NTFS 支持
System 项是操作系统的一部分或者只被操作系统使用
Temporary 项是临时的,应用程序不使用它时可以被删除
FileInfo myFile = new FileInfo(@"d:\AnswerQuestion\TestCSharp.pdb");
 
// 调用 Attributes.ToString() 返回逗号间隔的特性列表
lblInfo.Text = myFile.Attributes.ToString();
 
if (myFile.Attributes == FileAttributes.ReadOnly)
{
    // 这个 if 块的逻辑是错误的,仅当文件只含有只读属性时才成立
}
 
if ((myFile.Attributes & FileAttributes.ReadOnly) != 0)
{
    // 判断特性是不是含有 ReadOnly,需要进行位与运算
    // 如果 == 0,那么就不包含这个特性
}
 
// 设置 只读 特性并且保留其他所有原特性
myFile.Attributes = myFile.Attributes | FileAttributes.ReadOnly;
 
// 先或后与,下面这步会移除 ReadOnly 之外所有的特性
myFile.Attributes = myFile.Attributes & FileAttributes.ReadOnly;

 

使用通配符过滤文件

       DirectoryInfo 和 Directory 都提供了一种方式:可以根据搜索表达式来匹配特定的目录或文件。* 代表0或多个任意字符 ? 代表任意单个字符:

DirectoryInfo dir = new DirectoryInfo(@"d:\AnswerQuestion");
FileInfo[] files = dir.GetFiles("*.txt");
DirectoryInfo[] dirs = dir.GetDirectories();
foreach (FileInfo file in files)
{
    // ......
}
foreach (DirectoryInfo dir in dirs)
{
    // ......
}
// GetFiles() 和 GetDirectories() 仅搜索当前目录
// 如果要包含子目录进行搜索,需要使用递归

 

获取文件的版本信息

       文件版本信息是你在资源管理器中查看 EXE 或 DLL 文件时所看到的信息。版本信息通常包括版本号、生产组件的公司名称、商标信息等。

       FileInfo 和 File 都没有提供获取文件版本信息的方法。不过可以使用 System.Diagnostics.FileVersionInfo.GetVersionInfo() 获取 FileVersionInfo 对象。

FileVersionInfo 属性:

FileVersion、FileMajorPart
FileMinorPart、FileBuildPart
FilePrivatePart

一个典型的主版本号显示格式为:【主版本号】.【次版本号】.【内部版本号】.【专用版本号】
FileName 获取 FileVersionInfo 实例所描述的文件的名称
OriginalFilename 获取文件创建时的名称
InternalName 如果存在,则获取文件的内部名称
FileDescription 获取文件的描述
CompanyName 获取创建文件的公司名
ProductName 获取随文件一起发布的产品的名称
ProductVersion、ProductMajorPart
ProductMinorPart、ProductBuildPart
ProductPrivatePart

获取完整产品版本号(ProductVersion)
IsDebug 文件是否包含调试信息或是否在调试启用的情况下被编译
IsPatched 文件是否已修改且是否不同于相同版本号的原始文件
IsPreRelease 表示文件是开发版本,而不是用于商业目的的发行版本
IsPrivateBuild 是否是采用标准的发行过程开发的
IsSpecialBuild 文件是否为特殊的内部版本
Comments 获取与文件关联的注释
Language 获取版本信息块的默认语言字符串
LegalCopyright 获取所有适用于指定文件的版权声明
LegalTrademarks 获取应用到文件的商标和注册商标

 

 

Path 类

       如果你正在使用文件,那么也很有可能要用用到文件路径和目录路径。路径信息用普通的字符串保存。因此,有时需要字符串解析代码来操作这些字符。

       Path 类提供了若干个执行常见路径处理任务的静态辅助方法,Path.Combine()可以把一个完整的目录路径与那个目录中的任意文件名一起使用:

DirectoryInfo dirInfo = new DirectoryInfo(@"d:\AnswerQuestion");
string file = "test.txt";
string path = Path.Combine(dirInfo.FullName, file);

 

      为了防止规范化错误之类的风险,也可以使用 Path 类。

       规范化错误是一种特定类型的应用程序错误,它会在你的代码中假定用户提供的值总是符合标准格式时发生。规范化错误时一项低级但非常严重的技术,它们通常会导致用户可以执行某个本应该受到限制的动作。(一个恶名昭彰的规范化错误是 SQL 注入,其他形式的规范化错误可能发生在 路径 和 URL 中)。

       考虑下面的代码:

// 攻击者加入提供一个限定文件名,如: ..\filename
// 连接后的路径 WebApp\Documents\..\filename 将会从父目录 WepApp 获取文件
FileInfo file1 = new FileInfo(Server.MapPath("Documents\\" + txtBox.Text));
// 代码的修复其实很简单,借助一次 Path 类
// 使用 GetFileName() 仅提取字符串最终的文件名部分
string filename = Path.GetFileName(txtBox.Text);
FileInfo file2 = new FileInfo(Server.MapPath(Path.Combine("Documents", filename)));

 

       如果要处理 URL,可以用 Syestem.Uri 类型实现同样的操作。下面的代码演示了 删除查询字符串并确保它只指向给定的服务器和虚拟目录:

string uriString = "http://www.wrongsite.com/page.aspx?cmd=run";
Uri uri = new Uri(uriString);
 
// 获取 Uri 的绝对路径 page 的值现在是: page.aspx
string page = Path.GetFileName(uri.AbsolutePath);
 
// 创建正确的虚拟目录路径及合适的文件名
Uri baseUri = new Uri("http://www.rightsite.com");
uri = new Uri(baseUri, page);

 

Path 类最有用的方法:

Combine() 合并文件或子目录的路径
ChangeExtension() 修改字符串中当前文件的扩展名。如果未指定扩展名,当前扩展名会被删除
FetDirectoryName() 返回目录信息。它是第一个和最后一个目录分隔符(\)之间的文本
GetFileName() 返回路径的文件名部分
GetFileNameWithoutExtension() 与 GetFileName() 相似,但返回的字符串中忽略了扩展名
GetFullPath() 该方法对绝对路径无效。它按当前目录把相对路径变成绝对路径
GetPathRoot() 获取根目录的字符串信息(例如 C:\),对于相对目录,返回空引用
HasExtension() 如果路径以文件扩展名结束,则返回 true
IsPathRooted() 如果路径是绝对路径,则返回 true,如果是相对路径,则返回 false

 

       虽然 Path 类有从目录结构向下延伸的方法(在路径中加入子目录),但它没有提供任何返回上层目录的方法(从路径中删除子目录)。不过,可以在 Combine()里使用相对路径打破这一限制,然后用 GetFullPath()让它以正常的格式返回。

string path = @"c:\temp\subdir";
path = Path.Combine(path, "..");// path = c:\temp\subdir\..
path = Path.GetFullPath(path);  // path = c:\temp

posted on 2012-08-20 10:41  SkySoot  阅读(1783)  评论(0编辑  收藏  举报

导航