c# 高级编程 22章498页 【文件和流】【管理文件系统】

234 阅读5分钟

概述

读写 文件和目录时,可以使用简单的API, 也可以使用 先进的API 来提供更多的功能

  • 可以用 简单的API 读写文件
  • 可以用 得到更多功能,例如:
    • 可以 压缩数据
    • 利用 内存映射文件 和 管道 在不同的任务间 共享数据

还必须 区分 Windows.Runtime提供的类 和 .NET提供的类

  • 通用Windows平台(UWP)Windows应用程序中:
    • 不能 在任意目录中 访问文件系统
    • 只能访问 特定的目录
    • 可以 让用户选择文件
  • 可以混合Windows.Runtime的类和.NET的类,以同时使用.NET功能Windows运行库

管理 文件系统

  • FileSystemInfo: 表示 任何 文件系统对象 的基类
  • FileInfoFile:表示 文件系统上的 文件
    • FileInfo继承自FileSystemInfo
  • DirectoryInfoDirectory:表示 文件系统上的 文件夹
    • DirectoryInfo继承自FileSystemInfo
  • Path:其包含的 静态成员 可以用于 处理 路径名
  • DriveInfo:其 属性和方法 提供了 指定 驱动器 的信息

对比

使用哪个类,主要依赖于访问 文件夹或文件的 次数

  • Directory类和File
    • 只包含静态方法,不能被实例化
    • 如果只对文件夹或文件执行一个操作,使用这些类就很有用,因为可以省去 创建.NET对象的 系统开销
  • DirectoryInfo类和FileInfo
    • 实现了与Directory类和File类 大致相同的公共方法
    • 还拥有一些公共属性和构造函数
    • 成员都不是静态的,需要实例化这些类
    • 如果使用同一个对象执行多个操作,这些类就比较有效
    • 因为构造时它们将读取 文件系统对象的 身份验证 和 其他信息,无论对对象调用了多少方法,都不需要再读取这些信息

DriveInfo 检查驱动器信息

在处理 文件和目录 之前,先检查 驱动器 信息

  • GetDrives()方法 返回 DriveInfo 数组
        public static void Main()
        {
            DriveInfo[] drives = DriveInfo.GetDrives();
            foreach (DriveInfo drive in drives)
            {
                if (drive.IsReady)
                {
                    Console.WriteLine($"Drive name: {drive.Name}");
                    Console.WriteLine($"Format: {drive.DriveFormat}");
                    Console.WriteLine($"Type: {drive.DriveType}");
                    Console.WriteLine($"Root directory: {drive.RootDirectory}");
                    Console.WriteLine($"Volume label: {drive.VolumeLabel}");
                    Console.WriteLine($"Free space: {drive.TotalFreeSpace}");
                    Console.WriteLine($"Available space: {drive.AvailableFreeSpace}");
                    Console.WriteLine($"Total size: {drive.TotalSize}");

                    Console.WriteLine();
                }
            }
        }

输出:

Drive name: C:\
Format: NTFS
Type: Fixed
Root directory: C:\
Volume label: Windows
Free space: 45745283072
Available space: 45745283072
Total size: 128849014784

Mac电脑上,驱动器符 是不可用的。但也可以看到RamNetwork类型

Drive name: /
Format: hfs
Type: Fixed
Root directory: /
Volume label: /
Free space: 45745283072
Available space: 45745283072
Total size: 128849014784

Drive name: /dev
Format: devs
Type: Ram
Root directory: /dev
Volume label: /
Free space: 0
Available space: 0
Total size: 184832

Drive name: /net
Format: autofs
Type: Network
Root directory: /net
Volume label: /net
Free space: 0
Available space: 0
Total size: 0

Path类 和 Environment.SpecialFolder

Path类 :

  • 可以自动添加 缺少的分隔符
    • 相比之下,使用字符串拼接 文件路径 很容易遗漏分隔符
  • 基于 Windows 和 Unix 处理不同的需求

Path类 :

  • 提供一些静态方法
    • Path.Combine()
    • 提供路径的信息
    • 以相应的格式显示信息
  • 提供一些公共字段, 得到特定于平台的字符
    • VolumnSeparatorChar分隔硬盘(Windows下是 ":")
    • DirectorySeparatorChar分隔文件夹(Windows下是 "\" 反斜线)
    • AltDirectorySeparatorChar分隔文件(Windows下是 "/" 正斜线)
    • PathSeparator分隔多个路径(Windows下是 ";")
  • 帮助访问 特定于用户的 临时文件夹
    • GetTempPath()
    • GetTempFileName() 包括文件夹
    • GetRandomFileName() 只返回文件名,不包括文件夹
Path.Combine(@"D:\Projects", "ReadMe.txt");

Environment类的 SpecialFolder

  • 是一个 巨大的 枚举
  • 提供了许多 文件夹 的值
private static string GetDocumentsFolder() => Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

File类 创建文件

File.WriteAllText()方法

  • 文件名 和 写入文件的内容,都在一个调用中指定
        const string Sample1FileName = "Sample1.md";
        
        private static void CreateAFile()
        {
            string fileName = Path.Combine(GetDocumentsFolder(), Sample1FileName);
            File.WriteAllText(fileName, "Hello, World!");
        }

File类 和 FileInfo类 复制文件

没有复制完整文件夹的方法——必须复制文件夹中的每个文件 File:

File.Copy(fileName1, fileName2);

FileInfo:

var file = new FileInfo(fileName1);
file.CopyTo(fileName2);

实例化 FileInfoDirectoryInfo

给构造函数 传递 文件系统对象的 路径的 字符串


不存在

  • 如果路径代表的文件系统对象 不存在,那么 实例化时 不抛出异常
  • 而是在 第一次调用某个方法,实际需要相应的文件系统对象时,才抛出异常

可以通过Exists属性,来检查文件系统对象 是否 存在


不具有适当的类型

还可以通过Exists属性,来检查文件系统对象 是否 具有适当的类型

  • 如果实例化FileInfo时提供了文件夹路径,则返回false
  • 如果实例化DirectoryInfo时提供了文件路径,则返回false
  • 如果 在这种情况下 调用 FileInfoDirectoryInfo的 属性和方法, 并不一定会抛出异常
    • 只有在执行 不可能的 操作时,才抛出异常
      • 例如:想用FileInfo.Open()打开文件夹
    • 有时 会不抛出异常
      • 例如:对一个文件夹,想调用FileInfo的显示的创建时间的方法 (文件夹也有创建时间,所以不抛异常)
var file = new FileInfo(@"C:\Windows");
Console.WriteLine(file.Exists);    //输出false

移动和删除 文件和文件夹

FileInfoDirectoryInfo

  • MoveTo()
  • Delete() FileDirectory:
  • Move()
  • Delete()

访问 文件属性

File 类 提供了 静态方法

FileInfo 类提供了 实例方法

        FileInformation("./Program.cs");
        
        private static void FileInformation(string fileName)
        {
            var file = new FileInfo(fileName);
            Console.WriteLine($"Name: {file.Name}");
            Console.WriteLine($"Directory: {file.DirectoryName}");
            Console.WriteLine($"Read only: {file.IsReadOnly}");
            Console.WriteLine($"Extension: {file.Extension}");
            Console.WriteLine($"Length: {file.Length}");
            Console.WriteLine($"Creation time: {file.CreationTime:F}");
            Console.WriteLine($"Access time: {file.LastAccessTime:F}");
            Console.WriteLine($"File attributes: {file.Attributes}");
        }

输出:

Name: Program.cs
Directory: C:\ProfessionalCSharp7-main\ProfessionalCSharp7-main\FilesAndStreams\FilesAndStreamsSamples\WorkingWithFilesAndDirectories\bin\Debug\netcoreapp2.1
Read only: False
Extension: .cs
Length: 7423
Creation time: 202243011:26:05
Access time: 202243011:26:05
File attributes: Archive

修改 文件属性

FileInfo

  • 有一些属性 只有 get访问器,没有set访问器:
    • 文件名
    • 文件扩展名
    • 文件的长度
  • 有一些属性 有set访问器:
    • 创建时间
    • 最后一次访问的时间

乍看起来,修改文件属性很奇怪,但这很有用。

  • 如果程序只需要读取文件,删除它,再用新内容创建一个新文件。那么就可以选择修改这个文件的创建时间,复用这个文件
        private static void ChangeFileProperties()
        {
            Console.WriteLine(GetDocumentsFolder());
            string fileName = Path.Combine(GetDocumentsFolder(), Sample1FileName);
            var file = new FileInfo(fileName);
            if (!file.Exists)
            {
                Console.WriteLine($"Create the file {Sample1FileName} before calling this method");
                Console.WriteLine("You can do this by invoking this program with the -c argument");
                return;
            }

            Console.WriteLine($"creation time: {file.CreationTime:F}");
            file.CreationTime = new DateTime(2025, 12, 24, 15, 0, 0);
            Console.WriteLine($"creation time: {file.CreationTime:F}");
        }

输出:

creation time: 202243011:41:11
creation time: 2025122415:00:00

读写 文件

使用 字符串 读写文件

使用 一个字符串

  • File.ReadAllText
  • File.WriteAllText

每行 使用 一个字符串

  • File.ReadAllLines: 返回一个 字符串数组
    • 对 每一行 执行 不同的处理
    • 仍然需要将 完整的文件 读入内存
  • File.ReadLines: 返回IEnumerable<string>
    • 逐行读取
    • 不需要 等所有行 都 读取完
    • 在 读取完 之前,就可以遍历 IEnumerable<string>
  • File.WriteAllLines:
    • 参数1:文件名
    • 参数2: IEnumerable<string>
  • File.AppendAllLines:
    • 把 字符串 追加 到已有文件中
        private static void ReadingAFileLineByLine(string fileName)
        {
            string[] lines = File.ReadAllLines(fileName);
            int i = 1;
            foreach (var line in lines)
            {
                Console.WriteLine($"{i++}. {line}");
            }

            IEnumerable<string> lines2 = File.ReadLines(fileName);
            i = 1;
            foreach (var line in lines2)
            {
                Console.WriteLine($"{i++}. {line}");
            }
        }
        private static void WriteAFile()
        {
            string fileName = Path.Combine(GetDocumentsFolder(), "movies.txt");
            string[] movies =
            {
                "Snow White And The Seven Dwarfs",
                "Gone With The Wind",
                "Casablanca",
                "The Bridge On The River Kwai",
                "Some Like It Hot"
            };

            File.WriteAllLines(fileName, movies);

            string[] moreMovies =
            {
                "Psycho",
                "Easy Rider",
                "Star Wars",
                "The Matrix"
            };
            File.AppendAllLines(fileName, moreMovies);
        }

枚举 文件

处理多个文件时,可以使用Directory

  • Directory.GetFiles()
    • 返回:目录中 所有文件 的字符串数组
  • Directory.GetDirectories()
    • 返回:所有目录 的字符串数组

重载 允许传送搜索模式和SearchOption

  • 搜索模式:
    • 不允许 传递 正则表达式
    • 只传递 简单表达式
      • *表示 任意字符
      • 表示 单个字符
  • SearchOption:
    • AllDirectories: 遍历 所有子目录
    • TopDirectoriesOnly: 留在 顶级目录

遍历 很大的目录 时GetFiles()GetDirectories()在返回之前,需要完整的结果


遍历 很大的目录 时,可以用EnumerateFiles()EnumerateDirectories(), 它们使用IEnumerable<string> 立即开始 返回结果

        private static void DeleteDuplicateFiles(string directory, bool checkOnly)
        {
            IEnumerable<string> fileNames = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories);
            string previousFileName = string.Empty;
            foreach (string fileName in fileNames)
            {
                string previousName = Path.GetFileNameWithoutExtension(previousFileName);
                if (!string.IsNullOrEmpty(previousFileName) &&
                    previousName.EndsWith("Copy") &&
                    fileName.StartsWith(previousFileName.Substring(0, previousFileName.LastIndexOf(" - Copy"))))
                {
                    var copiedFile = new FileInfo(previousFileName);
                    var originalFile = new FileInfo(fileName);
                    if (copiedFile.Length == originalFile.Length)
                    {
                        Console.WriteLine($"delete {copiedFile.FullName}");
                        if (!checkOnly)
                        {
                            copiedFile.Delete();
                        }
                    }
                }
                previousFileName = fileName;
            }
        }