【C语言实战(72)】C语言文件系统实战:解锁目录与磁盘IO的奥秘

目录

  • 一、引言
  • 二、文件系统基础概念
    • [2.1 文件系统结构](#2.1 文件系统结构)
    • [2.2 核心操作](#2.2 核心操作)
  • 三、目录操作实战(跨平台)
    • [3.1 Linux 下目录操作(基于 dirent.h)](#3.1 Linux 下目录操作(基于 dirent.h))
      • [3.1.1 核心函数](#3.1.1 核心函数)
      • [3.1.2 实战](#3.1.2 实战)
    • [3.2 Windows 下目录操作(基于 windows.h)](#3.2 Windows 下目录操作(基于 windows.h))
      • [3.2.1 核心函数](#3.2.1 核心函数)
      • [3.2.2 实战](#3.2.2 实战)
  • [四、磁盘 IO 与文件属性实战](#四、磁盘 IO 与文件属性实战)
    • [4.1 获取文件属性](#4.1 获取文件属性)
      • [4.1.1 实战](#4.1.1 实战)
    • [4.2 磁盘空间查询](#4.2 磁盘空间查询)
      • [4.2.1 实战](#4.2.1 实战)
    • [4.3 实战优化](#4.3 实战优化)
  • 五、总结

一、引言

在 C 语言开发的广袤领域中,文件系统操作是至关重要的基础部分。无论是在操作系统开发、嵌入式系统编程,还是在数据处理、应用程序开发等方面,文件系统的有效运用都不可或缺。比如在操作系统中,文件系统负责管理磁盘空间、组织文件存储,为用户和应用程序提供数据的持久化存储和读取功能;在嵌入式系统里,对文件系统的高效操作能直接影响设备的性能和稳定性。

本文将深入探讨 C 语言在文件系统中的实战应用,着重围绕目录操作与磁盘 I/O 展开,帮助读者全面掌握 C 语言文件系统操作的核心技能和关键方法。

二、文件系统基础概念

2.1 文件系统结构

文件系统是操作系统用于明确存储设备(常见的如硬盘、U 盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。它由三部分组成:与文件管理有关的软件、被管理文件以及实施文件管理所需的数据结构。从用户角度看,文件系统主要涉及目录(文件夹)、文件和路径。

  • 目录(文件夹):是一种特殊的文件,它用于组织和管理文件,是文件系统的一种组织形式。目录可以包含文件和其他子目录,形成层次化的结构,方便用户对文件进行分类和查找。例如,在 Windows 系统中,"C:\Program Files" 就是一个目录,里面包含了各种应用程序的相关文件和子目录;在 Linux 系统中,"/usr/local" 也是一个常见的目录,用于存放本地安装的软件。
  • 文件:是数据的集合,以二进制或文本形式存储在磁盘上,有特定的文件名和文件扩展名,扩展名用于标识文件的类型。如 "example.txt" 是一个文本文件,"image.jpg" 是一个图像文件。文件可以包含各种信息,如文档内容、图像数据、程序代码等。
  • 路径 :用于指定文件或目录在文件系统中的位置。路径分为绝对路径和相对路径。
    • 绝对路径:从文件系统的根目录开始,完整地描述文件或目录的位置,是一个唯一确定的路径。在 Windows 系统中,绝对路径以盘符(如 C:、D:)开头,例如 "C:\Users\John\Documents\report.docx";在 Linux 系统中,绝对路径以根目录 "/" 开头,例如 "/home/user/Downloads/file.zip"。
    • 相对路径:相对于当前工作目录的路径,它不包含完整的目录结构,是一种相对位置的表示。假设当前工作目录是 "C:\Users\John\Documents",如果要访问该目录下的 "subfolder" 子目录中的 "file.txt" 文件,相对路径可以是 "subfolder/file.txt";在 Linux 系统中,如果当前工作目录是 "/home/user",要访问其下的 "projects" 目录中的 "code.py" 文件,相对路径可以是 "projects/code.py"。

2.2 核心操作

在文件系统中,有一些核心操作对于文件和目录的管理至关重要,这些操作是实现文件系统功能的基础。

  • 创建目录:用于在文件系统中新建一个目录,为用户提供一个新的文件组织空间。在实际应用中,当我们安装一个新的软件时,安装程序通常会在指定的目录下创建一系列的子目录,用于存放软件的各种文件,如可执行文件、配置文件、数据文件等。在 C 语言中,可以使用特定的函数来实现创建目录的操作,这在不同的操作系统下有不同的实现方式,后面会详细介绍。
  • 遍历目录:指的是按照一定的顺序访问目录中的所有文件和子目录,这在很多场景中都非常有用。比如在实现一个文件搜索工具时,就需要遍历指定目录及其所有子目录,查找符合条件的文件;在进行文件备份时,也需要遍历目录来获取所有需要备份的文件。通过遍历目录,我们可以对目录中的文件进行各种操作,如统计文件数量、计算文件总大小等。
  • 获取文件属性:能够获取文件的相关信息,如文件大小、修改时间、创建时间、文件权限等,这些属性对于了解文件的基本情况和进行文件管理非常重要。比如,我们可以根据文件的修改时间来判断文件是否是最新的版本;根据文件大小来合理安排磁盘空间。在 C 语言中,通过相应的函数可以获取这些文件属性,不同操作系统也有各自对应的函数和方法。

三、目录操作实战(跨平台)

3.1 Linux 下目录操作(基于 dirent.h)

3.1.1 核心函数

在 Linux 系统中,对目录进行操作时,dirent.h头文件提供了一系列重要的函数,其中最常用的三个函数是opendir、readdir和closedir。

  • opendir函数:用于打开一个目录,返回一个指向DIR结构的指针,该指针用于后续对目录的读取操作。如果打开目录失败,将返回NULL。其函数原型为:
c 复制代码
DIR *opendir(const char *name);

参数name是要打开的目录的路径名,可以是绝对路径,如/home/user/Documents,也可以是相对路径,如./Documents。例如:

c 复制代码
DIR *dp = opendir("/home/user/Documents");
if (dp == NULL) {
    perror("opendir");
    return;
}

上述代码尝试打开/home/user/Documents目录,如果打开失败,perror函数会输出错误信息。

  • readdir函数:用于从已打开的目录中读取下一个目录项,返回一个指向dirent结构的指针,该结构包含了目录项的相关信息,如文件名、文件类型等。当读取到目录末尾或发生错误时,返回NULL。其函数原型为:
c 复制代码
struct dirent *readdir(DIR *dirp);

参数dirp是由opendir函数返回的目录流指针。例如:

c 复制代码
struct dirent *entry;
while ((entry = readdir(dp))) {
    printf("%s\n", entry->d_name);
}

这段代码通过循环调用readdir函数,逐个读取目录中的条目,并打印出每个条目的文件名。

  • closedir函数:用于关闭一个已打开的目录流,释放相关资源。成功关闭时返回0,失败时返回-1。其函数原型为:
c 复制代码
int closedir(DIR *dirp);

同样,参数dirp是要关闭的目录流指针。在完成对目录的操作后,一定要调用closedir函数关闭目录,以避免资源泄漏。例如:

c 复制代码
if (closedir(dp) == -1) {
    perror("closedir");
    return;
}

上述代码关闭目录,并检查是否关闭成功,如果失败则输出错误信息。

3.1.2 实战

下面通过一个具体的代码示例来展示如何实现目录遍历函数,输出指定目录下所有文件与子目录名称:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>

// 递归遍历目录函数
void listFiles(const char *basePath) {
    char path[1000];
    struct dirent *dp;
    DIR *dir = opendir(basePath);

    if (!dir) {
        return;
    }

    while ((dp = readdir(dir)) != NULL) {
        // 跳过当前目录和上级目录
        if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
            continue;
        }

        // 拼接完整路径
        sprintf(path, "%s/%s", basePath, dp->d_name);
        printf("%s\n", path);

        // 如果是目录,递归遍历
        if (dp->d_type == DT_DIR) {
            listFiles(path);
        }
    }

    closedir(dir);
}

int main() {
    listFiles(".");
    return 0;
}

在上述代码中:

  • listFiles函数接受一个basePath参数,表示要遍历的目录路径。
  • 使用opendir函数打开目录,如果打开失败则直接返回。
  • 通过readdir函数循环读取目录中的条目,跳过当前目录(.)和上级目录(...)。
  • 对于每个条目,拼接其完整路径并输出。
  • 如果条目是目录(通过dp->d_type == DT_DIR判断),则递归调用listFiles函数继续遍历该子目录。
  • 最后使用closedir函数关闭目录。
  • 在main函数中,调用listFiles函数遍历当前目录(.)。

3.2 Windows 下目录操作(基于 windows.h)

3.2.1 核心函数

在 Windows 系统中,基于windows.h头文件进行目录操作时,常用的函数有FindFirstFile、FindNextFile和FindClose。

  • FindFirstFile函数:用于在指定目录中查找第一个符合条件的文件或目录,返回一个HANDLE类型的查找句柄,通过这个句柄可以对查找到的文件或目录进行后续操作。如果查找失败,返回INVALID_HANDLE_VALUE。其函数原型为:
c 复制代码
HANDLE FindFirstFile(
    LPCTSTR lpFileName,
    LPWIN32_FIND_DATA lpFindFileData
);

lpFileName参数指定要查找的文件名或目录名,可以包含通配符,如*表示任意字符,?表示任意单个字符,例如C:\Users\*.*表示查找C:\Users目录下的所有文件和目录;lpFindFileData是一个指向WIN32_FIND_DATA结构的指针,用于接收查找到的文件或目录的信息,该结构包含了文件或目录的各种属性,如文件名、文件大小、修改时间等。

  • FindNextFile函数:在使用FindFirstFile找到第一个文件或目录后,通过该函数查找下一个符合条件的文件或目录。如果成功找到下一个文件或目录,返回TRUE,否则返回FALSE。其函数原型为:
c 复制代码
BOOL FindNextFile(
    HANDLE hFindFile,
    LPWIN32_FIND_DATA lpFindFileData
);

hFindFile是由FindFirstFile函数返回的查找句柄;lpFindFileData同样是指向WIN32_FIND_DATA结构的指针,用于接收下一个查找到的文件或目录的信息。

  • FindClose函数:用于关闭由FindFirstFile函数返回的查找句柄,释放相关资源。成功关闭时返回TRUE,失败时返回FALSE。其函数原型为:
c 复制代码
BOOL FindClose(
    HANDLE hFindFile
);

hFindFile即为要关闭的查找句柄,在完成文件或目录查找操作后,务必调用此函数关闭句柄,防止资源泄漏。

3.2.2 实战

以下是一个实现跨平台目录遍历工具的代码示例,该工具可以在 Windows 和 Linux 系统下均能列出目录内容:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#endif

// 跨平台目录遍历函数
void listFiles(const char *path) {
#ifdef _WIN32
    WIN32_FIND_DATA findFileData;
    HANDLE hFind = FindFirstFile(path, &findFileData);

    if (hFind == INVALID_HANDLE_VALUE) {
        printf("FindFirstFile failed (%d)\n", GetLastError());
        return;
    }

    do {
        printf("%s\n", findFileData.cFileName);
    } while (FindNextFile(hFind, &findFileData) != 0);

    FindClose(hFind);
#else
    DIR *dp = opendir(path);
    struct dirent *entry;

    if (dp == NULL) {
        perror("opendir");
        return;
    }

    while ((entry = readdir(dp))) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        printf("%s\n", entry->d_name);
    }

    closedir(dp);
#endif
}

int main() {
#ifdef _WIN32
    listFiles("C:\\Users\\*.*");
#else
    listFiles("/home/user");
#endif
    return 0;
}

在上述代码中:

  • 使用#ifdef _WIN32和#else预处理器指令来区分 Windows 和 Linux 平台。
  • 在 Windows 平台下:
    • 使用FindFirstFile函数查找指定目录(这里是C:\Users目录下的所有文件和目录)中的第一个文件或目录,获取查找句柄hFind。
    • 通过do-while循环,利用FindNextFile函数不断查找下一个文件或目录,并打印其文件名。
    • 最后使用FindClose函数关闭查找句柄。
  • 在 Linux 平台下:
    • 使用opendir函数打开指定目录(这里是/home/user目录),获取目录流指针dp。
    • 通过while循环,利用readdir函数读取目录中的条目,跳过当前目录和上级目录,打印其他条目的文件名。
    • 最后使用closedir函数关闭目录流。
  • 在main函数中,根据不同的平台调用listFiles函数遍历相应的目录。

四、磁盘 IO 与文件属性实战

4.1 获取文件属性

在 C 语言中,获取文件属性是一项重要的操作,不同操作系统下有不同的实现方式。

  • Linux 系统:使用stat函数来获取文件的属性,该函数定义在<sys/stat.h>头文件中。stat函数通过文件名获取文件或文件系统的状态信息,返回一个struct stat结构体,其中包含了文件的各种属性,如文件大小(st_size)、修改时间(st_mtime)、访问时间(st_atime)、创建时间(st_ctime)等。其函数原型为:
c 复制代码
int stat(const char *pathname, struct stat *statbuf);

pathname是要获取属性的文件路径名,可以是绝对路径或相对路径;statbuf是一个指向struct stat结构体的指针,用于存储获取到的文件属性信息。例如:

c 复制代码
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>

int main() {
    struct stat file_stat;
    if (stat("test.txt", &file_stat) == -1) {
        perror("stat");
        return 1;
    }

    printf("文件大小: %ld 字节\n", file_stat.st_size);
    printf("修改时间: %s", ctime(&file_stat.st_mtime));

    return 0;
}

上述代码中,首先定义了一个struct stat结构体变量file_stat,然后调用stat函数获取test.txt文件的属性,将结果存储在file_stat中。如果获取失败,perror函数会输出错误信息。最后,打印文件的大小和修改时间,ctime函数用于将时间戳转换为人类可读的时间格式。

  • Windows 系统:使用GetFileAttributesEx函数来获取文件的属性,该函数定义在<windows.h>头文件中。GetFileAttributesEx函数可以获取文件的详细属性信息,包括文件大小、修改时间、创建时间、访问时间等。它通过一个WIN32_FILE_ATTRIBUTE_DATA结构体来返回文件属性。其函数原型为:
c 复制代码
BOOL GetFileAttributesEx(
    LPCTSTR lpFileName,
    GET_FILEEX_INFO_LEVELS fInfoLevelId,
    LPVOID lpFileInformation
);

lpFileName是要获取属性的文件路径名;fInfoLevelId指定获取文件信息的级别,通常使用GetFileExInfoStandard表示获取标准的文件信息;lpFileInformation是一个指向WIN32_FILE_ATTRIBUTE_DATA结构体的指针,用于接收文件属性信息。例如:

c 复制代码
#include <windows.h>
#include <stdio.h>

// 将FILETIME转换为本地时间并打印
void ShowFileTime(PFILETIME lptime) {
    FILETIME ftLocal;  // 本地时间
    SYSTEMTIME st;    // 系统时间
    // 先转换为本地时间(北京时间)
    FileTimeToLocalFileTime(lptime, &ftLocal);
    // 将本地时间转换为系统时间
    FileTimeToSystemTime(&ftLocal, &st);
    printf("%4d年%02d月%02d日 %02d:%02d:%02d\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
}

// 显示文件大小
void ShowFileSize(DWORD dwFileSizeHigh, DWORD dwFileSizeLow) {
    ULONGLONG liFileSize;  // 64位的
    liFileSize = dwFileSizeHigh;
    liFileSize <<= 32;  // 左移32,移动到高32位上
    liFileSize += dwFileSizeLow;  // 加上低32位
    printf("文件大小:\t%I64u 字节\n", liFileSize);
}

int main() {
    WIN32_FILE_ATTRIBUTE_DATA wfad;
    // 获取文件属性
    if (!GetFileAttributesEx(TEXT("C:\\Users\\test.txt"), GetFileExInfoStandard, &wfad)) {
        printf("获取文件属性失败: %d\n", GetLastError());
        return 1;
    }

    printf("修改时间:\t");
    ShowFileTime(&wfad.ftLastWriteTime);
    ShowFileSize(wfad.nFileSizeHigh, wfad.nFileSizeLow);  // 显示文件大小

    return 0;
}

在上述代码中,首先定义了ShowFileTime函数用于将FILETIME类型的时间转换为本地时间并打印,ShowFileSize函数用于显示文件大小。在main函数中,定义了WIN32_FILE_ATTRIBUTE_DATA结构体变量wfad,调用GetFileAttributesEx函数获取C:\Users\test.txt文件的属性,将结果存储在wfad中。如果获取失败,打印错误信息。最后,调用ShowFileTime函数打印文件的修改时间,调用ShowFileSize函数打印文件大小。

4.1.1 实战

下面通过一个完整的代码示例来实现 "文件信息查看工具",该工具可以输出指定文件的大小(字节)、最后修改时间,并且可以在 Linux 和 Windows 系统下运行:

c 复制代码
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
// 将FILETIME转换为本地时间并打印
void ShowFileTime(PFILETIME lptime) {
    FILETIME ftLocal;  // 本地时间
    SYSTEMTIME st;    // 系统时间
    // 先转换为本地时间(北京时间)
    FileTimeToLocalFileTime(lptime, &ftLocal);
    // 将本地时间转换为系统时间
    FileTimeToSystemTime(&ftLocal, &st);
    printf("%4d年%02d月%02d日 %02d:%02d:%02d\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
}
// 显示文件大小
void ShowFileSize(DWORD dwFileSizeHigh, DWORD dwFileSizeLow) {
    ULONGLONG liFileSize;  // 64位的
    liFileSize = dwFileSizeHigh;
    liFileSize <<= 32;  // 左移32,移动到高32位上
    liFileSize += dwFileSizeLow;  // 加上低32位
    printf("文件大小:\t%I64u 字节\n", liFileSize);
}
#else
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#endif

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("用法: %s 文件名\n", argv[0]);
        return 1;
    }
#ifdef _WIN32
    WIN32_FILE_ATTRIBUTE_DATA wfad;
    // 获取文件属性
    if (!GetFileAttributesEx(argv[1], GetFileExInfoStandard, &wfad)) {
        printf("获取文件属性失败: %d\n", GetLastError());
        return 1;
    }
    printf("修改时间:\t");
    ShowFileTime(&wfad.ftLastWriteTime);
    ShowFileSize(wfad.nFileSizeHigh, wfad.nFileSizeLow);  // 显示文件大小
#else
    struct stat file_stat;
    if (stat(argv[1], &file_stat) == -1) {
        perror("stat");
        return 1;
    }
    printf("文件大小: %ld 字节\n", file_stat.st_size);
    printf("修改时间: %s", ctime(&file_stat.st_mtime));
#endif
    return 0;
}

在上述代码中:

  • 使用#ifdef _WIN32和#else预处理器指令来区分 Windows 和 Linux 平台。
  • 在 Windows 平台下:
    • 定义ShowFileTime函数和ShowFileSize函数分别用于处理时间和显示文件大小。
    • 在main函数中,检查命令行参数是否正确,若不正确则提示用法并返回。
    • 使用GetFileAttributesEx函数获取文件属性,若获取失败则打印错误信息并返回。
    • 调用ShowFileTime函数打印文件的修改时间,调用ShowFileSize函数打印文件大小。
  • 在 Linux 平台下:
    • 在main函数中,同样检查命令行参数。
    • 使用stat函数获取文件属性,若获取失败则通过perror输出错误信息并返回。
    • 直接打印文件大小和使用ctime函数转换后的修改时间。

4.2 磁盘空间查询

查询磁盘空间在不同操作系统下也有各自的实现方式,这对于监控磁盘使用情况、管理存储资源等非常重要。

  • Linux 系统:使用statvfs函数来查询磁盘空间信息,该函数定义在<sys/statvfs.h>头文件中。statvfs函数用于获取指定文件系统的磁盘空间统计信息,返回一个struct statvfs结构体,其中包含了文件系统的各种信息,如总块数(f_blocks)、空闲块数(f_bfree)、每个块的大小(f_bsize)等。通过这些信息可以计算出磁盘的总空间和可用空间。其函数原型为:
c 复制代码
int statvfs(const char *path, struct statvfs *buf);

path是要查询磁盘空间的文件系统路径,可以是挂载点,如/表示根文件系统;buf是一个指向struct statvfs结构体的指针,用于存储获取到的磁盘空间信息。例如:

c 复制代码
#include <stdio.h>
#include <sys/statvfs.h>

int main() {
    struct statvfs diskInfo;
    if (statvfs("/", &diskInfo) == -1) {
        perror("statvfs");
        return 1;
    }

    unsigned long long totalSize = diskInfo.f_blocks * diskInfo.f_bsize;
    unsigned long long freeSize = diskInfo.f_bfree * diskInfo.f_bsize;

    printf("磁盘总空间: %llu 字节\n", totalSize);
    printf("磁盘可用空间: %llu 字节\n", freeSize);

    return 0;
}

上述代码中,定义了struct statvfs结构体变量diskInfo,调用statvfs函数获取根文件系统(/)的磁盘空间信息,将结果存储在diskInfo中。如果获取失败,perror函数会输出错误信息。然后,通过结构体中的f_blocks和f_bsize计算磁盘总空间,通过f_bfree和f_bsize计算磁盘可用空间,并打印出来。

  • Windows 系统:使用GetDiskFreeSpaceEx函数来查询磁盘空间信息,该函数定义在<windows.h>头文件中。GetDiskFreeSpaceEx函数可以获取指定磁盘分区的剩余空间信息,包括总大小、可用空间以及当前用户可使用的自由空间等。其函数原型为:
c 复制代码
BOOL GetDiskFreeSpaceEx(
    LPCTSTR lpDirectoryName,
    PULARGE_INTEGER lpFreeBytesAvailableToCaller,
    PULARGE_INTEGER lpTotalNumberOfBytes,
    PULARGE_INTEGER lpTotalNumberOfFreeBytes
);

lpDirectoryName是要查询的磁盘分区路径,如C:表示 C 盘;lpFreeBytesAvailableToCaller是一个指向ULARGE_INTEGER结构体的指针,用于接收当前用户可使用的自由空间大小;lpTotalNumberOfBytes是一个指向ULARGE_INTEGER结构体的指针,用于接收磁盘总大小;lpTotalNumberOfFreeBytes是一个指向ULARGE_INTEGER结构体的指针,用于接收磁盘总的空闲空间大小。例如:

c 复制代码
#include <windows.h>
#include <stdio.h>

int main() {
    ULARGE_INTEGER freeBytesAvailable, totalBytes, totalFreeBytes;
    if (!GetDiskFreeSpaceEx(TEXT("C:\\"), &freeBytesAvailable, &totalBytes, &totalFreeBytes)) {
        printf("获取磁盘空间信息失败: %d\n", GetLastError());
        return 1;
    }

    printf("磁盘总空间: %I64u 字节\n", totalBytes.QuadPart);
    printf("磁盘可用空间: %I64u 字节\n", freeBytesAvailable.QuadPart);

    return 0;
}

在这段代码中,定义了三个ULARGE_INTEGER结构体变量freeBytesAvailable、totalBytes和totalFreeBytes,分别用于存储当前用户可使用的自由空间大小、磁盘总大小和磁盘总的空闲空间大小。调用GetDiskFreeSpaceEx函数获取 C 盘的磁盘空间信息,若获取失败则打印错误信息并返回。最后,打印磁盘总空间和可用空间。

4.2.1 实战

下面是一个实现 "磁盘空间监控工具" 的代码示例,该工具可以输出当前磁盘的使用率,并且支持跨平台:

c 复制代码
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/statvfs.h>
#endif

int main() {
#ifdef _WIN32
    ULARGE_INTEGER freeBytesAvailable, totalBytes, totalFreeBytes;
    if (!GetDiskFreeSpaceEx(TEXT("C:\\"), &freeBytesAvailable, &totalBytes, &totalFreeBytes)) {
        printf("获取磁盘空间信息失败: %d\n", GetLastError());
        return 1;
    }
    double usedPercent = (1.0 - (double)freeBytesAvailable.QuadPart / (double)totalBytes.QuadPart) * 100;
    printf("C盘使用率: %.2f%%\n", usedPercent);
#else
    struct statvfs diskInfo;
    if (statvfs("/", &diskInfo) == -1) {
        perror("statvfs");
        return 1;
    }
    unsigned long long totalSize = diskInfo.f_blocks * diskInfo.f_bsize;
    unsigned long long freeSize = diskInfo.f_bfree * diskInfo.f_bsize;
    double usedPercent = (1.0 - (double)freeSize / (double)totalSize) * 100;
    printf("磁盘使用率: %.2f%%\n", usedPercent);
#endif
    return 0;
}

在上述代码中:

  • 同样使用#ifdef _WIN32和#else预处理器指令区分 Windows 和 Linux 平台。
  • 在 Windows 平台下:
    • 使用GetDiskFreeSpaceEx函数获取 C 盘的磁盘空间信息,包括总大小和可用空间。
    • 计算磁盘使用率,公式为(1 - 可用空间大小 / 总空间大小) * 100,并将结果打印出来,保留两位小数。
  • 在 Linux 平台下:
    • 使用statvfs函数获取根文件系统(/)的磁盘空间信息,包括总块数和空闲块数,结合块大小计算出总空间和可用空间。
    • 按照同样的公式计算磁盘使用率并打印,保留两位小数。如果获取磁盘空间信息失败,perror函数会输出错误信息。

4.3 实战优化

在进行大文件读写时,直接读写可能会导致频繁的磁盘 I/O 操作,这会严重影响读写效率。使用缓冲区是一种有效的优化方法,其原理是在内存中开辟一块缓冲区,当进行文件读写时,先将数据读取到缓冲区或者先将数据写入缓冲区,而不是直接与磁盘进行频繁交互。只有当缓冲区满了或者操作结束时,才一次性将缓冲区的数据写入磁盘或从磁盘读取数据填充缓冲区,这样可以大大减少磁盘 I/O 次数,提高读写效率。

下面通过代码示例对比有缓冲区和无缓冲区的读写效率:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define BUFFER_SIZE 4096 // 4KB缓冲区大小
#define FILE_SIZE (1024 * 1024 * 100) // 100MB文件大小

// 无缓冲区读写
void readWriteWithoutBuffer(const char *filename) {
    FILE *file = fopen(filename, "wb");
    if (file == NULL) {
        perror("fopen");
        return;
    }

    // 写入数据
    clock_t startWrite = clock();
    for (int i = 0; i < FILE_SIZE; i++) {
        fputc('A', file);
    }
    clock_t endWrite = clock();
    fclose(file);

    // 读取数据
    file = fopen(filename, "rb");
    if (file == NULL) {
        perror("fopen");
        return;
    }
    clock_t startRead = clock();
    char ch;
    while ((ch = fgetc(file)) != EOF);
    clock_t endRead = clock();
    fclose(file);

    double writeTimeWithoutBuffer = (double)(endWrite - startWrite) / CLOCKS_PER_SEC;
    double readTimeWithoutBuffer = (double)(endRead - startRead) / CLOCKS_PER_SEC;

    printf("无缓冲区写入时间: %.6f 秒\n", writeTimeWithoutBuffer);
    printf("无缓冲区读取时间: %.6f 秒\n", readTimeWithoutBuffer);
}

//

五、总结

通过本文的学习,我们深入了解了C语言文件系统中目录操作与磁盘I/O的关键知识和实战技巧。从文件系统的基础概念,包括目录、文件和路径的结构,到创建目录、遍历目录以及获取文件属性等核心操作,再到Linux和Windows系统下的目录操作实战,以及磁盘I/O与文件属性获取、磁盘空间查询的实践,最后探讨了大文件读写的优化方法。我们全面掌握了C语言在文件系统领域的应用。

文件系统操作在C语言编程中占据着举足轻重的地位,无论是开发小型应用程序,还是构建大型系统,对文件系统的熟练运用都能为程序的功能实现和性能优化提供有力支持。希望读者在今后的学习和实践中,能够进一步探索C语言文件系统的更多功能和应用场景,不断提升自己的编程能力。

相关推荐
The Last.H2 小时前
Educational Codeforces Round 185 (Rated for Div. 2)A-C
c语言·c++·算法
未来之窗软件服务4 小时前
幽冥大陆(三十七)文件系统路径格式化——东方仙盟筑基期
前端·javascript·文件系统·仙盟创梦ide·东方仙盟
松涛和鸣4 小时前
DAY20 Optimizing VS Code for C/C++ Development on Ubuntu
linux·c语言·开发语言·c++·嵌入式硬件·ubuntu
unclecss4 小时前
从 0 到 1 手写 Linux 调试器:ptrace 系统调用与断点原理
linux·运维·服务器·c语言·ptrace
fashion 道格5 小时前
从地图导航到数据结构:解锁带权有向图的邻接链表奥秘
c语言·数据结构·链表
猿大叔~6 小时前
面试必问!Linux 下 C/C++ 内存对齐深度解析:从底层原理到实战避坑
linux·c语言·面试
C++ 老炮儿的技术栈7 小时前
用密码学安全随机数生成256位密钥
c语言·开发语言·c++·windows·安全·密码学·visual studio
swibyn7 小时前
【无标题】
c语言·素数
口袋物联7 小时前
设计模式之单例模式在 C 语言中的应用(含 Linux 内核实例)
c语言·单例模式·设计模式