C#学习笔记12 IO

本篇一,二,三基于https://www.cnblogs.com/c7jie/p/12799728.html ,

一 入门

1. 什么是I/O

在C# 中,I/O体系整体分为三个部分,后台存储流、装饰器流、流适配器,具体划分如下图所示:

image-20200428213627235

2.Stream 基类

C#中,所有流都是继承自Stream类,Stream类定义了流应该具有的行为和属性,使得开发人员可以忽略底层的操作系统和基础设备的具体细节。C#对流的处理忽略了读流和写流的区别,使其更像是一个管道,方便数据通信。流涉及到三个基本操作:

  • 读取 - 将数据从流中传输到数据结构中
  • 写入 - 将数据从数据源写入流中
  • 查找 - 对流中操作的当前位置进行查找和修改

因为流的特性,可能并不是所有的流都支持这三种操作,所以Stream提供了三个属性,以方便确认流是否支持这三种操作:

public abstract bool CanRead { get; } // 获取指示当前流是否支持读取的值
public abstract bool CanWrite { get; } // 获取指示当前流是否支持写入功能的值
public abstract bool CanSeek { get; } // 获取指示当前流是否支持查找功能的值

以上这三个属性均由子类根据自身特性确认是否支持读取、写入、查找,可能三个属性不会都为true,但绝对不会都为false。

下面是一些常见的流:

  • FileStream 用来操作文件的流
  • MemoryStream 操作内存的流
  • BufferedStream 缓存流,用来增强其他流的操作性能
  • NetworkStream 使用网络套接字进行操作的流
  • PipeStream 通过匿名和命名管道进行读取和写入
  • CryptoStream 用于将数据流链接到加密转换

3.操作

C#对IO进一步扩展,并提供了流压缩和解压缩(System.IO.Compression),搜索和枚举文件系统元素(System.IO.Enumeration),提供用于使用内存映射文件的类(System.IO.MemoryMappedFiles)等内容。

读取流里的数据

public abstract int Read (byte[] buffer, int offset, int count);
public virtual int ReadByte ();
public abstract int Read (byte[] buffer, int offset, int count);

必须以返回值为0作为流的读完判断依据。

public virtual int ReadByte ();

这个方法很简单,每次从流里读取一个字节的数据,如果读取完成返回-1。可能有人会疑惑了,这个方法明明是读取一个字节,也就是个byte,那为什么返回类型是int呢?很简单,因为byte没有负数,而int有。所以,当返回值不等于-1的时候,可以放心的类型转换为byte。

把数据写入流

public abstract void Write (byte[] buffer, int offset, int count);
public virtual void WriteByte (byte value);

关闭或销毁流

流在操作完成之后,需要将其关闭以释放流所持有的文件或IO设备等资源。很多人在使用电脑的时候,不能用QQ发送在本地已经打开的excel文件,它会提示文件被占用无法传输。这就是因为Excel打开了这个文件,就持有一个文件相关的流,所以QQ无法发送。解决办法很简单,关掉excel软件即可。回到当前,也就是我们在使用完成之后必须关闭流。

那么我们该如何关闭流呢?调用以下方法:

public virtual void Close ();

C#虽然设置了Close方法,但是并不支持开发者在编写程序的时候手动调用Close方法,更推荐使用:

public void Dispose ();

这个方法会将释放流所持有使用的资源,并关闭流。

当前需要注意的一个地方是,在把流关闭或释放之前把流里的数据推送到基础设备,即调用:

public abstract void Flush ();

有一些流设置了自动推送功能,如果遇到这种流则不需要手动调用该方法。

对于流来说,一旦销毁或关闭,这个流就无法二次使用了,所以调用了Close、Dispose之后再次尝试读取/写入流都会报错

二 文件操作

1. 文件、目录和路径

  • 相对路径指的是,相对程序所在目录目标文件所在的目录路径
  • 绝对路径指的是从系统或者网站的目录起点开始文件所在的位置,也就是说无论程序在哪都能通过绝对路径访问到对应文件
  • 物理路径是指文件在磁盘的路径,划分依据与之前的两种并不一致,所以不是并列关系(啥玩意?)
  • 网络路径是指网络或文件是在网络服务上部署的,通过URI访问的路径信息

1.1 File和FileInfo

C# 提供了两个访问文件的入口,File和FileInfo这两个类。

我们先来观察一下两个类的声明方式有什么不一样的:

public static class File;
public sealed class FileInfo : System.IO.FileSystemInfo;

通过两个类的声明方式,可以看出File是一个工具类,而FileInfo则是文件对象。

所以,File更多的用在快速操作文件并不需要长时间多次使用同一个文件的场景,而FileInfo则适合同一个文件的多次使用。

1.1.1 File工具类

我们先来看下File支持哪些操作:

a.文件读取

public static byte[] ReadAllBytes (string path);
public static string[] ReadAllLines (string path);
public static string[] ReadAllLines (string path, System.Text.Encoding encoding);
public static string ReadAllText (string path);
public static string ReadAllText (string path, System.Text.Encoding encoding);
public static System.Collections.Generic.IEnumerable<string> ReadLines (string path);

先从名称上分析方法应该是什么,应该具有哪些功能?

  • ReadAllBytes以二进制的形式一次性把文件全部读出来
  • ReadAllLines打开文本文件,将文件内容一行一行的全部读出来并返回
  • ReadAllText打开文件,并将文件所有内容一次性读出来
  • ReadLines 这是一个新的方法,根据返回值和方法名称,可以判断它应该与ReadAllLines有着类似的行为

ReadLInes和ReadAllLines的区别:

  • ReadAllLines返回的是字符串数组,所以该方法会一次性将文件内容全部读出
  • ReadLines返回的是一个可枚举对象,根据之前在Linq系列和集合系列的知识,我们能判断出,这个方法不会立即返回数据

所以我们很轻易的就能得出,ReadAllLines不会过久的持有文件对象,但是不适合操作大文件;ReadLines对于大文件的操作更擅长一些,但是可能会更久的持有文件

b.写入文件

public static void AppendAllLines (string path, System.Collections.Generic.IEnumerable<string> contents);
public static void AppendAllLines (string path, System.Collections.Generic.IEnumerable<string> contents, System.Text.Encoding encoding);
public static void AppendAllText (string path, string contents);
public static void AppendAllText (string path, string contents, System.Text.Encoding encoding);
public static void WriteAllBytes (string path, byte[] bytes);
public static void WriteAllLines (string path, string[] contents, System.Text.Encoding encoding);
public static void WriteAllText (string path, string contents);
public static void WriteAllText (string path, string contents, System.Text.Encoding encoding);

来,我们简单看一下这几个方法具体作用:

  • AppendAllLines:追加行到文件末尾
  • AppendAllText :将字符串内容追加到文件末尾
  • WriteBytes:将字节数组写到文件里,如果文件有内容就覆盖原有内容
  • WriteAllLines:按行写入文件中,如果文件有内容则覆盖原有内容
  • WriteAllText:将内容写入文件,如果文件有内容则覆盖原有内容

在使用File写入文件的时候,如果文件不存在则会自动创建文件。

c. 复制文件

File类提供了简单易用的复制文件功能,只需要指定源文件和新文件即可:

public static void Copy (string sourceFileName, string destFileName);
public static void Copy (string sourceFileName, string destFileName, bool overwrite);

这两个方法对的作用就是将 sourceFileName复制为destFileName。第一个方法不允许复制为已存在的文件,也就是说如果destFileName已存在则报错。第二个方法则通过overwrite指定是否覆盖。

d.移动文件

与复制文件相同的使用方式,File提供了移动文件的方法:

public static void Move (string sourceFileName, string destFileName);
public static void Move (string sourceFileName, string destFileName, bool overwrite);

注意事项与复制文件一致。

e.删除文件

public static void Delete (string path);

1.1.2 FileInfo 对象类

FileInfo提供了文件的创建、复制、删除、移动和打开等属性和实例方法。我们先来看看,如果创建一个FileInfo:

public FileInfo (string fileName);

通过指定文件路径,来换取一个FileInfo对象,如果fileName指定的是目录则会提示错误。

好,现在我们已经可以获取一个FileInfo对象实例了,那么一起来看看FileInfo支持哪些内容吧:

a. 先来看看文件的基本属性

public override bool Exists { get; }

文件是否存在,等效于File.Existss(string path)。

public string DirectoryName { get; }

获取文件所在目录的完整路径(绝对路径)。

public System.IO.DirectoryInfo Directory { get; }

获取文件所在目录的目录类型实例。

public long Length { get; }

获取文件的大小,单位是字节。

public override string Name { get; }

获取文件名,包括文件的扩展名。

b. 文件的操作

对于FileInfo实例来说,对于文件的操作大多都是基于流来完成的(这部分请留意下一篇内容),这里先看一下它的实例方法:

public System.IO.StreamWriter AppendText ();//创建一个流适配器,在适配器里追加文本到文件中
public System.IO.FileInfo CopyTo (string destFileName);//将现有文件复制到新文件,并返回新文件的实例,不支持覆盖
public System.IO.FileInfo CopyTo (string destFileName, bool overwrite);//根据orverwrite确定是否覆盖
public System.IO.FileStream Create ();//创建当前对象代表的文件,并返回一个文件流
public System.IO.StreamWriter CreateText ();//与AppendText类似,但会覆盖文件原有内容
public override void Delete ();//删除文件
public void MoveTo (string destFileName);// 将文件移动到新文件,不支持覆盖已存在文件
public void MoveTo (string destFileName, bool overwrite);// 根据overwrite确定是否覆盖
public System.IO.FileStream Open (System.IO.FileMode mode);// 根据模式打开文件
public System.IO.FileStream Open (System.IO.FileMode mode, System.IO.FileAccess access);//指定权限和模式,打开文件
public System.IO.FileStream OpenRead ();//打开一个只能读取的文件流
public System.IO.StreamReader OpenText ();//打开一个读流适配器
public System.IO.FileStream OpenWrite ();// 打开一个只能写的流

1.2 Directory和DirectoryInfo

与之前的类似,Directory也是个工具类,DirectoryInfo则代表目录实例。

1.2.1 Directory

先来个简单的:

a. 创建目录:

public static System.IO.DirectoryInfo CreateDirectory (string path);

如果目录已存在,则跳过创建,直接返回指定路径的DirectoryInfo实例

b.是否存在:

public static bool Exists (string path);

返回是否存在这个目录。

c.返回目录下的所有文件

public static string[] GetFiles (string path);

d. 返回目录下的所有子目录:

public static string[] GetDirectories (string path);
public static string[] GetDirectories (string path, string searchPattern);
public static string[] GetDirectories (string path, string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public static string[] GetDirectories (string path, string searchPattern, System.IO.SearchOption searchOption);

除了上文提到的 GetDirectories 方法可以直接返回目录下所有子目录以外,还有一组方法也可以枚举出当前目录下的子目录:

public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path);

枚举 path 目录下的所有子目录。

public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path, string searchPattern);

searchPattern,搜索名称字符串,可以包含有效文本路径和通配符(* 和 ?)的组合,但不支持正则表达式。

public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path, string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories (string path, string searchPattern, System.IO.SearchOption searchOption);

这两个方法放在一起讲,这两个是对上一个方法的增强和补充。其中 EnumerationOptions 是类,可以配置查询的条件;SearchOption 是个枚举,选择只查询当前目录的子目录名称还是继续深入查询子孙目录。

e.查看目录下的所有文件-补充

与子目录查询相同,Directory也支持这么几组查询方法:

public static string[] GetFiles (string path);
public static string[] GetFiles (string path, string searchPattern);
public static string[] GetFiles (string path, string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public static string[] GetFiles (string path, string searchPattern, System.IO.SearchOption searchOption);

从参数上看,可以看出来这是返回子目录下的文件列表。其中使用 searchPattern查询名称,enumerationOptions 作为查询条件,searchOption 作为查询的深度。

同样,查询文件也可以使用枚举方法:

public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path);
public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path, string searchPattern);
public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path, string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public static System.Collections.Generic.IEnumerable<string> EnumerateFiles (string path, string searchPattern, System.IO.SearchOption searchOption);

f.获取当前目录

public static string GetCurrentDirectory ();

在程序中调用这个方法可以获取程序执行时的目录,如果是在调试阶段,目录是指程序的主方法所在目录;如果在发布之后,也就是运行阶段,该目录指程序所在目录。

g.获取上级目录

public static System.IO.DirectoryInfo GetParent (string path);

获取传入目录的上级目录信息。

h.目录移动

public static void Move (string sourceDirName, string destDirName);

sourceDirName 移动到 destDirName,其中destDirName所代表的目录不能纯在。这个方法有个很有意思的特点,它也支持移动文件。也就是说,如果sourceDirNanme指向的是一个文件,那么destDirName也必须是一个文件类型的路径字符串。

i.删除目录

public static void Delete (string path);//删除 path所代表的目录,如果目录非空则提示无法删除
public static void Delete (string path, bool recursive);// recursive指示是否同时删除子目录和文件

以上是Directory类的一些常用方法,当然还有更多的内容留待小伙伴一起发掘。传送门==>https://docs.microsoft.com/zh-cn/dotnet/api/system.io.directory?view=netcore-3.1

1.2.2 DirectoryInfo

之前的篇幅我们介绍了Directory的工具类所支持的方法,接下来我们看一下 DirectoryInfo有哪些属性和方法吧。

public DirectoryInfo (string path);

初始化的方式很简单,直接传递一个目录的路径字符串,就可以获取一个目录信息类了。

接下来看看,DirectoryInfo支持的属性:

public override bool Exists { get; }// 目录是否存在
public override string Name { get; }// 目录名称,不是路径
public System.IO.DirectoryInfo Parent { get; }//如果有上级目录,则返回上级目录,如果没有则返回 null
public System.IO.DirectoryInfo Root { get; }//获取目录的根目录

我们路过了DirectoryInfo的属性,看到了它一部分特点,那么我们该怎么使用呢?

public void Create ();

创建目录信息所代表的目录,如果目录已存在,则不会有任何变化 。如果这个目录的父目录也不存在,则自动创建父目录

public System.IO.DirectoryInfo CreateSubdirectory (string path);

创建 pathi指定的子目录。

public override void Delete ();

如果当前目录是空目录,调用可直接删除,如果非空则会提示错误。

public void Delete (bool recursive);

根据参数 recursive指定是否删除当前目录的子目录。

public System.IO.DirectoryInfo[] GetDirectories ();
public System.IO.DirectoryInfo[] GetDirectories (string searchPattern);
public System.IO.DirectoryInfo[] GetDirectories (string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public System.IO.DirectoryInfo[] GetDirectories (string searchPattern, System.IO.SearchOption searchOption);

获取子目录的数组,参数与 Directory 的同名方法一致。

public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories ();
public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories (string searchPattern);
public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories (string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public System.Collections.Generic.IEnumerable<System.IO.DirectoryInfo> EnumerateDirectories (string searchPattern, System.IO.SearchOption searchOption);

返回一个子目录信息的可枚举集合。

public System.IO.FileInfo[] GetFiles ();
public System.IO.FileInfo[] GetFiles (string searchPattern);
public System.IO.FileInfo[] GetFiles (string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public System.IO.FileInfo[] GetFiles (string searchPattern, System.IO.SearchOption searchOption);

嗯,依旧类似的写法,获取文件信息的数组

public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles ();
public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles (string searchPattern);
public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles (string searchPattern, System.IO.EnumerationOptions enumerationOptions);
public System.Collections.Generic.IEnumerable<System.IO.FileInfo> EnumerateFiles (string searchPattern, System.IO.SearchOption searchOption);

返回文件的可枚举集合。

public void MoveTo (string destDirName);

把当前目录移动到对应的目录。

1.3. Path

Path的中文名称有路径的意思,所以Path类就是路径类,C#把Path设置为工具类,路径的实例被区分为文件和目录了。以下是它的定义:

public static class Path

路径是描述文件和目录的位置的字符串,路径并不一定指向硬盘上,换句话说就是路径不一定是物理路径也有可能是虚拟路径或者网络路径。在不同的操作系统和平台上,路径有着不同的表现,所以Path类是对不同平台行为的统一抽象。具体的路径表示需要参照具体的系统表示形式。

那么我们先来看看Path为我们提供了哪些内容,让我们一睹为快:

1.3.1 字段

public static readonly char AltDirectorySeparatorChar;
public static readonly char DirectorySeparatorChar;

这两个是特定系统下的目录分隔符,其中AltDirectorySeparatorChar表示正斜线(/),DirectorySeparatorChar 表示反斜线(\)。为什么说是特定系统下的目录分隔符呢,因为Windows环境对两种分隔符都支持,但是Unix和类Unix系统只支持 / 作为目录分隔符。所以如果系统需要跨平台支持,则最好使用 AltDirectorySeparatorChar作为目录分隔符来使用。

public static readonly char PathSeparator;

这个字段返回在环境变量中分隔路径字符串的平台特定的分隔符。Windows中返回一个分号(;),其他平台可能会有不一样的表现。

public static readonly char VolumeSeparatorChar;

这个表示卷分隔符,是个很有意思的特定。对于Linux系统来说并没有类似于Windows一样的卷,所以该字段会返回一个/ ,而Windows中例如:

D:\Temp\ 这个目录则会返回冒号(:)。

1.3.2 方法

介绍完了字段,我们来看看Path给我们提供了哪些方法吧。

先从最常用的说起吧:

public static string Combine (params string[] paths);
public static string Combine (string path1, string path2);
public static string Combine (string path1, string path2, string path3);
public static string Combine (string path1, string path2, string path3, string path4);

这一组方法用来拼接路径,除第一个参数外,每个参数都应当是相对于之前参数拼接结果路径的相对路径。如果后续出现了绝对路径,那之前计算出的路径信息则会全部抛弃,重新计算。

以下是一个示例:

string[] paths = {@"d:\archives", "2001", "media", "images"};
string fullPath = Path.Combine(paths);
Console.WriteLine(fullPath);            

paths = new string[] {@"d:\archives\", @"2001\", "media", "images"};
fullPath = Path.Combine(paths);
Console.WriteLine(fullPath); 

paths = new string[] {"d:/archives/", "2001/", "media", "images"};
fullPath = Path.Combine(paths);
Console.WriteLine(fullPath); 
// Windows系统下的执行结果
//    d:\archives\2001\media\images
//    d:\archives\2001\media\images
//    d:/archives/2001/media\images
//
// 类Unix系统的执行结果
//    d:\archives/2001/media/images
//    d:\archives\/2001\/media/images
//    d:/archives/2001/media/images 

继续下一个方法:

public static string GetFullPath (string path, string basePath);
public static string GetFullPath (string path);

获取相对路径的绝对路径,其中 path 是相对路径,basePath是绝对路径。如果指定basePath,则从basePath根据path计算全路径。

public static string GetRelativePath (string relativeTo, string path);

返回从一个路径到另一个路径的相对路径,其中relativeTo是源路径,path为目标路径。其中relativeTo始终是目录,或者被认为是目录。

public static string GetDirectoryName (string path);

返回路径path里的目录信息,例如:“C:\Directory\SubDirectory\test.txt” ,返回"C:\Directory\SubDirectory",如果path是目录,则返回其上级目录的路径字符串。

public static string Join (string path1, string path2, string path3, string path4);
public static string Join (string path1, string path2, string path3);
public static string Join (params string[] paths);

与Combine方法差不多,不过Join方法是把所以参数均按照相对目录来拼接。说白就是后面遇到绝对路径也不会重新计算

说完了目录的一些操作,我们看看Path对文件路径提供了哪些支持:

public static string GetFileName (string path);

获取路径里的文件名,例如说:“C:\mydir\myfile.ext”,返回结果就是“myfile.ext”,也就是说这个方法会返回携带后缀名的文件名。因为文件名本身就包含后缀名。

public static string GetFileNameWithoutExtension (string path);

返回不带后缀名的文件名,与GetFileName类似,但是不含文件格式后缀。

public static bool HasExtension (string path);

确定是否包含后缀名,也称格式名或者扩展名。

public static string GetExtension (string path);

返回所代表的文件的后缀名。

public static string ChangeExtension (string path, string extension);

修改文件的后缀名。

这些是Path的常用方法,大家有个印象就好。

1.4 FileSystemInfo

文件系统信息,这是FileInfo和DirectoryInfo的两个类的基类,它定义了文件系统中文件和目录共有的一些属性和方法。接下来让我们简单看一看。

先来看一下类的声明:

public abstract class FileSystemInfo : MarshalByRefObject, System.Runtime.Serialization.ISerializable

一个abstract类,这个标记意味着这个类是一个抽象类,抽象类不能直接实例化,所以我们可能不会自己去直接实例化一个FileSystemInfo了。

所以我们先略过FileSystemInfo的构造函数,直接看属性和方法。

public System.IO.FileAttributes Attributes { get; set; }

获取或者设置当前文件或目录的特性,这个特性是一个枚举,而且是一个位标记的枚举类型。

名称含义
Archive32此文件标记为包含在增量备份操作中。 每当修改文件时,Windows 会设置该属性,并且在增量备份期间处理文件时,备份软件应进行清理该属性。
Compressed2048此文件是压缩文件。
Device64留待将来使用。
Directory16此文件是一个目录。 Directory 在 Windows、Linux 和 macOS 上受支持。
Encrypted16384此文件或目录已加密。 对于文件来说,表示文件中的所有数据都是加密的。 对于目录来说,表示新创建的文件和目录在默认情况下是加密的。
Hidden2文件是隐藏的,因此没有包括在普通的目录列表中。 Hidden 在 Windows、Linux 和 macOS 上受支持。
IntegrityStream32768文件或目录包括完整性支持数据。 在此值适用于文件时,文件中的所有数据流具有完整性支持。 此值将应用于一个目录时,所有新文件和子目录在该目录中和默认情况下应包括完整性支持。
Normal128该文件是没有特殊属性的标准文件。 仅当其单独使用时,此特性才有效。 Normal 在 Windows、Linux 和 macOS 上受支持。
NoScrubData131072文件或目录从完整性扫描数据中排除。 此值将应用于一个目录时,所有新文件和子目录在该目录中和默认情况下应不包括数据完整性。
NotContentIndexed8192将不会通过操作系统的内容索引服务来索引此文件。
Offline4096此文件处于脱机状态, 文件数据不能立即供使用。
ReadOnly1文件为只读文件。 ReadOnly 在 Windows、Linux 和 macOS 上受支持。 在 Linux 和 macOS 上,更改 ReadOnly 标记是权限操作。
ReparsePoint1024文件包含一个重新分析点,它是一个与文件或目录关联的用户定义的数据块。 ReparsePoint 在 Windows、Linux 和 macOS 上受支持。
SparseFile512此文件是稀疏文件。 稀疏文件一般是数据通常为零的大文件。
System4此文件是系统文件。 即,该文件是操作系统的一部分或者由操作系统以独占方式使用。
Temporary256文件是临时文件。 临时文件包含当执行应用程序时需要的,但当应用程序完成后不需要的数据。 文件系统尝试将所有数据保存在内存中,而不是将数据刷新回大容量存储,以便可以快速访问。 当临时文件不再需要时,应用程序应立即删除它。

通过以下方式进行判断:

FileSystemInfo fsi;
bool isXXX = (fsi.Attributes & FileAttributes.XXX) == FileAttributes.XXX; 
public DateTime CreationTime { get; set; }
public DateTime CreationTimeUtc { get; set; }

返回文件/目录的创建时间,其中UTC指协调世界时 。

public string Extension { get; }

获取文件的文件后缀名(扩展名),带点号(.)。

public virtual string FullName { get; }
public abstract string Name { get; }

都是返回文件或目录的名称,不过FullName返回的是全路径名称,Name只返回了文件名。

public DateTime LastAccessTime { get; set; }
public DateTime LastAccessTimeUtc { get; set; }

获取或设置文件最后一次访问的时间,该属性的返回值并不是严格意义上的最后一次访问时间,因为部分系统不会及时更新。

public DateTime LastWriteTime { get; set; }
public DateTime LastWriteTimeUtc { get; set; }

最后一次修改时间,可以自己设置或修改,类似与LastAccessTime,可能不是正确的值。

三 流的使用

1. 简单的IO流读写文件

先来看一部分代码:

class Program
{
    static void Main(string[] args)
    {
        var directory = Directory.GetCurrentDirectory();
        var program = File.Open("../../../Program.cs", FileMode.OpenOrCreate);
        // program = File.Open("Program.cs", FileMode.OpenOrCreate);
        var buffers = new byte[1024];// 创建一个8k的缓存区
        var list = new List<byte>();
        while(true)
        {
            int length = program.Read(buffers, 0, buffers.Length);
            if(length <=0)
            {
                break;
            }
            list.AddRange(buffers.Take(length));
        }

        program.Close();
        Console.WriteLine(list.Count);
    }
}

到目前为止,打开了一个流读取当前程序源文件,每次读取到一个字节数组里,然后将数据放到list集合里,在读取完成后关闭这个流。虽然以上流并没有太多意义,但是基本演示了一下流的读取操作。

注意到注释的那行代码和上一行代码的区别吗?在编译阶段,Directory.GetCurrentDirectory()表示源文件所在目录;在运行阶段,表示程序编译完成的DLL所在目录。

输出结果:

image-20200503181653684

2. 使用流适配器

普通的流读取和写入都是使用字节数组,我们要以字符为操作单位,这才方便,这在实际开发中非常不方便,所以C#又在流的基础上开发了流适配器。C#中流适配器是指XXXReader或者XXXWriter,这种类在初始化的时候传入一个流作为操作对象,然后对这个流进行一定的封装,简化了其操作方法。

现在以StreamReader为例,来看看具体如何使用:

public StreamReader (System.IO.Stream stream);
public StreamReader (System.IO.Stream stream, System.Text.Encoding encoding);

这里是两个以流为主要参数的构造方法,不同的是一个指定了文本编码 encoding,另一个默认使用系统的文本编码。

public StreamReader (string path);
public StreamReader (string path, System.Text.Encoding encoding);

这两个是通过指定文件的路径,然后打开一个StreamReader对象。

现在我们来看下这个Reader对象有哪些方法或者说我们常用的方法有哪些吧:

public override int Read ();
public override int Read (char[] buffer, int index, int count);

读取字符,与普通的流不同的是,StreamReader的读取是以字符为单位的读取,而char类型与int之间存在一定的转换关系,所以方法Read()的返回值是int。

public override string ReadLine ();

这个方法的意思是一次读一行,如果读到末尾则返回null。

public override string ReadToEnd ();

这个方法的意思是一次性读完剩余的数据然后返回一个字符串。

照例,Reader提供了流的关闭和销毁方法:

public override void Close ();

现在让我们来改造一下第一节的示例程序:

class Program
{
    static void Main(string[] args)
    {
        var reader = new StreamReader("Program.cs");
        while(true)
        {
            var str = reader.ReadLine();
            if(str == null)
            {
                break;
            }
            Console.WriteLine(str);
        }
        reader.Close();
    }
}

这段代码的意思是读取当前主程序的文件,然后按行打印。打印结果应该类似于:

image-20200503214306875

这是我本地的代码文件。

简单的介绍了一下StreamReader,然后我们来看一下StreamWriter如何使用。按照我的惯例,先从构造函数来:

public StreamWriter (System.IO.Stream stream);
public StreamWriter (System.IO.Stream stream, System.Text.Encoding encoding);

与StreamReader类似,打开一个允许写的流。

public StreamWriter (string path);
public StreamWriter (string path, bool append);
public StreamWriter (string path, bool append, System.Text.Encoding encoding);

打开path对应的文件,然后将数据写入到文件中。append表示当文件存在时,数据是追加到文件末尾还是覆盖文件。

然后看一下它的方法:

public override void Write (string value);
public override void Write (string format, object arg0, object arg1, object arg2);
public override void Write (string format, params object[] arg);

Write方法提供了很多个重载版本,但是我们只需要关注这三个即可。第一个很简单,直接写一个字符串。如果把第二个方法和第三个方法结合起来,然后再联系一下String.Format我想很多小伙伴就知道怎么使用了。没错,这两个方法的效果就是下面这种方式:

var value = string.Format(string format, params object[] arg);
writer.Write(value);
public override void WriteLine (string value);
public override void WriteLine (string format, object arg0, object arg1, object arg2);
public override void WriteLine (string format, params object[] arg);

同时C#也添加了一组WriteLine的方法,该方法与Write不同的是,WriteLine会在写入数据后向流里追加一个换行符,所以这个方法是写入一行。

不过,在使用Writer的时候需要注意以下这三个方法:

public override void Flush ();
public override void Close ();
protected override void Dispose (bool disposing);

其中Dispose(销毁)是受保护的方法,一般场景中遇不到。Flush表示将Writer的数据推送到基础流里,Close表示关闭Writer顺便关闭基础流。

在C#中,对Close动作进行了进一步优化。当调用Close方法的时候,系统会自动调用Flush方法将数据推送到基础流中。那么,为什么还提供了Flush呢?因为如果要操作一个大数据或者数据的来源是分批,这时候为了保证之前的数据不会丢失就需要我们手动调用Flush把数据推送给基础流了。

嗯,所以我们来写个程序验证一下:

class Program
{
    static void Main(string[] args)
    {
        var reader = new StreamReader("Program.cs");
        var writer = new StreamWriter("Program.cs.txt");
        while(true)
        {
            var str = reader.ReadLine();
            if(str == null)
            {
                break;
            }
            Console.WriteLine(str);
            writer.WriteLine(str);
        }
        //writer.Close();
        reader.Close();
    }
}

如示例,在注释了 writer.Close(); 之后,程序依然会生成一个Program.cs.txt 文件,但文件是空的。这时候取消注释,就会发现Program已经复制到了Program.cs.txt里。

3. 常用的有哪些适配器流

1. BinaryReader

用特定的编码将基元数据类型读作二进制值

2. BinaryWriter

将二进制中的基元类型写入流并支持用特定的编码写入字符串

3.StringReader

从字符串中读取字符串

4.StringWriter

将信息写入字符串中

5.XmlReader/XmlWriter

对xml文件的快速操作

这几个是出镜率较高的,但仍有很多选手藏在幕后,并非是它们不出镜,而是它们经常活跃在特定的领域里。所以这里就没有做过多的介绍。

四 FileAccess、FileMode、FileShare

FileAccess

字段

Read1对文件的读访问。 可从文件中读取数据。 与 Write 组合以进行读写访问。
ReadWrite3对文件的读写访问权限。 可从文件读取数据和将数据写入文件。
Write2文件的写访问。 可将数据写入文件。 与 Read 组合以进行读写访问。

FileMode

Append6若存在文件,则打开该文件并查找到文件尾,或者创建一个新文件。 这需要 Append 权限。 FileMode.Append 只能与 FileAccess.Write 一起使用。 试图查找文件尾之前的位置时会引发 IOException 异常,并且任何试图读取的操作都会失败并引发 NotSupportedException 异常。
Create2指定操作系统应创建新文件。 如果此文件已存在,则会将其覆盖。 这需要 Write 权限。 FileMode.Create 等效于这样的请求:如果文件不存在,则使用 CreateNew;否则使用 Truncate。 如果该文件已存在但为隐藏文件,则将引发 UnauthorizedAccessException异常。
CreateNew1指定操作系统应创建新文件。 这需要 Write 权限。 如果文件已存在,则将引发 IOException异常。
Open3指定操作系统应打开现有文件。 打开文件的能力取决于 FileAccess 枚举所指定的值。 如果文件不存在,引发一个 FileNotFoundException 异常。
OpenOrCreate4指定操作系统应打开文件(如果文件存在);否则,应创建新文件。 如果用 FileAccess.Read 打开文件,则需要 Read权限。 如果文件访问为 FileAccess.Write,则需要 Write权限。 如果用 FileAccess.ReadWrite 打开文件,则同时需要 ReadWrite权限。
Truncate5指定操作系统应打开现有文件。 该文件被打开时,将被截断为零字节大小。 这需要 Write 权限。 尝试从使用 FileMode.Truncate 打开的文件中进行读取将导致 ArgumentException 异常。

FileShare

Delete4允许随后删除文件。
Inheritable16使文件句柄可由子进程继承。 Win32 不直接支持此功能。
None0谢绝共享当前文件。 文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。
Read1允许随后打开文件读取。 如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
ReadWrite3允许随后打开文件读取或写入。 如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
Write2允许随后打开文件写入。 如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
            FileStream fileStream1 = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
            //fileStream1.Close();
            FileStream fileStream2 = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);

结果

这个FileShare是没有先后之分的,如果第一个流Read,第二个流shape为Write,那么还是会报错。只有他们Access被所有流(除了自己)允许的时候才不会报错。

代码块

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace StudyIO
{
    class Program
    {
        static void Main(string[] args)
        {
            //StudyFile.Test();
            //Base();
            //FileInfo();
            //DirectoryInfo();
            //StudyPath();
            //ReadStream();
            //WriteStream();
            //StreamReader();
            //StudyFileShare();
            StudyBinaryReader();
            //StudyBinaryWriter();


        }
        static void Base()
        {
            FileStream F = new FileStream("test.dat",
                        FileMode.OpenOrCreate, FileAccess.ReadWrite);
            Console.WriteLine("长度:" + F.Length);
            for (int i = 1; i <= 25; i++)
            {
                F.WriteByte((byte)i);
            }
            F.Position = 0;
            byte[] buffer = new byte[5];
            F.Read(buffer, 0, 4);
            foreach (byte b in buffer)
            {
                Console.WriteLine(b);
            }
            F.Position = 0;
            int result;
            do
            {
                result = F.ReadByte();
                Console.Write(result + " ");
            } while (result != -1);

            Console.WriteLine();
            Console.WriteLine("长度:" + F.Length);
            Console.WriteLine("位置:" + F.Position);
            F.Dispose();
            Console.ReadKey();
        }

        static void StudyFileShare()
        {
            FileStream fileStream1 = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
            //fileStream1.Close();
            FileStream fileStream2 = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
        }
        static void FileInfo()
        {
            FileInfo fileInfo = new FileInfo("test.txt");
            Console.WriteLine(fileInfo.FullName);
            Console.WriteLine(fileInfo.DirectoryName);
            Console.WriteLine("这个是目录实例,不是路径:" + fileInfo.Directory);
            Console.WriteLine(fileInfo.Length);
            Console.WriteLine(fileInfo.Name);
            Console.WriteLine(fileInfo.Attributes);
            Console.WriteLine(fileInfo.CreationTimeUtc);
            Console.WriteLine(fileInfo.Exists);
            Console.WriteLine(fileInfo.Length);
            Console.WriteLine(fileInfo.Name);
        }
        static void DirectoryInfo()
        {
            DirectoryInfo directoryInfo = new DirectoryInfo("/study/C#/ConsoleApp/StudyIO/bin/Debug/netcoreapp3.1");
            Console.WriteLine(directoryInfo.FullName);
            Console.WriteLine(directoryInfo.Name);
            Console.WriteLine(directoryInfo.Root);
            Console.WriteLine(directoryInfo.Parent);
            Console.WriteLine(directoryInfo.Extension);

            Foreach("遍历输出子目录", directoryInfo.GetDirectories());
            Foreach("搜索子目录", directoryInfo.GetDirectories("*2"));
            EnumerationOptions enumerationOptions = new EnumerationOptions() { RecurseSubdirectories = true };
            Foreach("enumerationOptions条件搜索", directoryInfo.GetDirectories("A2", enumerationOptions));
            Foreach("SearchOption条件搜索", directoryInfo.GetDirectories("*", SearchOption.AllDirectories));
            Foreach("枚举搜索", directoryInfo.EnumerateDirectories("*2"));

            Foreach("文件搜索", directoryInfo.GetFiles("*"));
            Foreach("枚举文件搜索", directoryInfo.EnumerateFiles("*"));
        }
        static void StudyPath()
        {
            Console.WriteLine(Path.AltDirectorySeparatorChar);
            Console.WriteLine(Path.DirectorySeparatorChar);
            Console.WriteLine(Path.PathSeparator);
            Console.WriteLine(Path.VolumeSeparatorChar);

            string[] paths = { @"d:\archives", @"d:\2001", "media", "images" };
            string fullPath = Path.Combine(paths);
            Console.WriteLine(fullPath);

            paths = new string[] { @"d:\archives\", @"2001\", "media", "images" };
            fullPath = Path.Combine(paths);
            Console.WriteLine(fullPath);

            paths = new string[] { "d:/archives/", "2001/", "media", "images" };
            fullPath = Path.Combine(paths);
            Console.WriteLine(fullPath);

            Console.WriteLine(Path.GetFullPath("/"));
            Console.WriteLine(Path.GetFullPath("netcoreapp3.1", "E:/乱七八糟的/C#/ConsoleApp/就乱来"));
            Console.WriteLine(Path.GetRelativePath("E:/study/C#/ConsoleApp", "E:/study/C#/ConsoleApp/StudyIO/bin/Debug/netcoreapp3.1"));
            Console.WriteLine(Path.GetDirectoryName("E:/study/C#/ConsoleApp/StudyIO/bin/Debug/netcoreapp3.1/test.txt"));
        }

        static void Foreach(String caption, IEnumerable iEnumerable)
        {
            Console.WriteLine(caption);
            foreach (var i in iEnumerable)
            {
                Console.WriteLine(i);
            }
        }
        static void ReadStream()
        {
            var directory = Directory.GetCurrentDirectory();
            var program = File.Open("../../../Program.cs", FileMode.OpenOrCreate);
            // program = File.Open("Program.cs", FileMode.OpenOrCreate);
            var buffers = new byte[1024];// 创建一个8k的缓存区
            var list = new List<byte>();
            while (true)
            {
                int length = program.Read(buffers, 0, buffers.Length);
                if (length <= 0)
                {
                    break;
                }
                list.AddRange(buffers.Take(length));
            }

            program.Close();
            Foreach("打印输出", list.ToArray());
            Console.WriteLine(list.Count);
        }
        static void WriteStream()
        {
            var directory = Directory.GetCurrentDirectory();
            Console.WriteLine(Path.GetFullPath("Program.cs", directory));
            FileStream program = File.Open("../../../Program.cs", FileMode.OpenOrCreate);
            var buffers = new byte[1024];// 创建一个8k的缓存区
            var list = new List<byte>();
            while (true)
            {
                int length = program.Read(buffers, 0, buffers.Length);
                if (length <= 0)
                {
                    break;
                }
                list.AddRange(buffers.Take(length));
            }
            program.Close();
            Console.WriteLine($"已读取:{list.Count}");
            FileStream tempr = File.Open("../../../Program_01.cs", FileMode.OpenOrCreate);
            tempr.Write(list.ToArray(), 0, list.Count);
            tempr.Close();
        }
        static void StreamReader()
        {
            var reader = new StreamReader("../../../Program.cs");
            while (true)
            {
                var str = reader.ReadLine();
                if (str == null)
                {
                    break;
                }
                Console.WriteLine(str);
            }
            reader.Close();
        }

        static void StudyBinaryReader()          // 读取文件
        {
            FileStream fs = new FileStream("BinaryStreamTest.txt", FileMode.Open, FileAccess.Read);
            BinaryReader r = new BinaryReader(fs);

            //以二进制方式读取文件中的内容 会自动根据读取类型大小自动变动位置
            int i = r.ReadInt32();
            float f = r.ReadSingle();
            double d = r.ReadDouble();
            bool b = r.ReadBoolean();
            string s = r.ReadString();
            Console.WriteLine(i);
            Console.WriteLine(f);
            Console.WriteLine(d);
            Console.WriteLine(b);
            Console.WriteLine(s);

            r.Close();
            fs.Close();
        }

        static void StudyBinaryWriter()     // 写入文件
        {
            FileStream fs = new FileStream("BinaryStreamTest.txt", FileMode.OpenOrCreate);
            BinaryWriter w = new BinaryWriter(fs);

            //以二进制方式向创建的文件中写入内容 
            w.Write(666);                   //  整型
            w.Write(66.6f);                // 浮点型
            w.Write(6.66);                // double型
            w.Write(true);                 // 布尔型
            w.Write("六六六");         // 字符串型

            w.Close();
            fs.Close();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值