Linux C语言拷贝文件,高效实现方式是什么?

99ANYc3cd6
预计阅读时长 33 分钟
位置: 首页 C语言 正文

使用 read()write() 系统调用(最底层、最灵活)

这是最基本、最高效的方法,因为它直接与内核交互,没有额外的缓冲层开销,它适用于大文件拷贝,并且可以精确控制每次读取和写入的字节数。

linux c语言拷贝文件
(图片来源网络,侵删)

核心思想:

  1. 打开源文件和目标文件。
  2. 循环执行以下操作,直到文件末尾: a. 从源文件中读取一块数据到缓冲区。 b. 将缓冲区中的数据写入目标文件。
  3. 关闭所有打开的文件描述符。

完整代码示例 (copy_syscall.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 用于 read, write, close
#include <fcntl.h>  // 用于 open
#include <errno.h>  // 用于 errno
#include <string.h> // 用于 strerror
#define BUFFER_SIZE 4096 // 定义一个缓冲区大小,通常是 4KB,与磁盘块大小对齐
void copy_file(const char *src_path, const char *dest_path) {
    int src_fd, dest_fd;
    ssize_t bytes_read, bytes_written;
    char buffer[BUFFER_SIZE];
    // 1. 打开源文件 (只读)
    src_fd = open(src_path, O_RDONLY);
    if (src_fd == -1) {
        fprintf(stderr, "Error opening source file '%s': %s\n", src_path, strerror(errno));
        exit(EXIT_FAILURE);
    }
    // 2. 打开目标文件 (只写,如果文件不存在则创建,如果存在则清空)
    // O_CREAT: 如果文件不存在则创建
    // O_WRONLY: 只写
    // O_TRUNC: 如果文件已存在,则将其长度截断为0
    // 0644: 文件权限,所有者读写,组和其他用户只读
    dest_fd = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dest_fd == -1) {
        fprintf(stderr, "Error opening destination file '%s': %s\n", dest_path, strerror(errno));
        close(src_fd); // 确保关闭已打开的源文件
        exit(EXIT_FAILURE);
    }
    // 3. 循环读取和写入
    while ((bytes_read = read(src_fd, buffer, BUFFER_SIZE)) > 0) {
        bytes_written = write(dest_fd, buffer, bytes_read);
        if (bytes_written != bytes_read) {
            // 写入的字节数不等于读取的字节数,说明出错
            fprintf(stderr, "Error writing to destination file.\n");
            close(src_fd);
            close(dest_fd);
            exit(EXIT_FAILURE);
        }
    }
    // 4. 检查 read 是否出错 (返回 -1)
    if (bytes_read == -1) {
        fprintf(stderr, "Error reading from source file: %s\n", strerror(errno));
        close(src_fd);
        close(dest_fd);
        exit(EXIT_FAILURE);
    }
    // 5. 关闭文件描述符
    close(src_fd);
    close(dest_fd);
    printf("File copied successfully from '%s' to '%s'\n", src_path, dest_path);
}
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
        return EXIT_FAILURE;
    }
    copy_file(argv[1], argv[2]);
    return EXIT_SUCCESS;
}

如何编译和运行:

# 编译
gcc -o copy_syscall copy_syscall.c
# 运行 (假设有一个名为 a.txt 的源文件)
./copy_syscall a.txt b.txt
# 验证
ls -l b.txt
cat b.txt

使用 C 标准库 fopen(), fread(), fwrite() (更简单、可移植)

这种方法使用 C 语言标准库,代码更简洁,可移植性更好(可以在其他 Unix-like 系统甚至 Windows 上编译运行),标准库内部通常会使用缓冲,对小文件拷贝来说非常方便。

核心思想:

  1. 使用 fopen() 以二进制模式打开源文件和目标文件。
  2. 使用 fread() 读取数据到缓冲区。
  3. 使用 fwrite() 将缓冲区数据写入目标文件。
  4. 使用 fclose() 关闭文件指针。

完整代码示例 (copy_cstdio.c)

#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 4096
void copy_file_cstdio(const char *src_path, const char *dest_path) {
    FILE *src_file, *dest_file;
    char buffer[BUFFER_SIZE];
    size_t bytes_read;
    // 1. 以二进制模式 ("rb", "wb") 打开文件,防止特殊字符被解释
    src_file = fopen(src_path, "rb");
    if (src_file == NULL) {
        perror("Error opening source file");
        exit(EXIT_FAILURE);
    }
    dest_file = fopen(dest_path, "wb");
    if (dest_file == NULL) {
        perror("Error opening destination file");
        fclose(src_file); // 确保关闭已打开的源文件
        exit(EXIT_FAILURE);
    }
    // 2. 循环读取和写入
    while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0) {
        if (fwrite(buffer, 1, bytes_read, dest_file) != bytes_read) {
            perror("Error writing to destination file");
            fclose(src_file);
            fclose(dest_file);
            exit(EXIT_FAILURE);
        }
    }
    // 3. 检查 fread 是否出错
    if (ferror(src_file)) {
        perror("Error reading from source file");
    }
    // 4. 关闭文件指针
    fclose(src_file);
    fclose(dest_file);
    printf("File copied successfully using C stdio.\n");
}
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
        return EXIT_FAILURE;
    }
    copy_file_cstdio(argv[1], argv[2]);
    return EXIT_SUCCESS;
}

如何编译和运行:

# 编译
gcc -o copy_cstdio copy_cstdio.c
# 运行
./copy_cstdio a.txt c.txt

使用 mmap() (内存映射文件,性能极高)

对于非常大的文件,mmap() 是一种非常高效的方法,它将文件直接映射到进程的虚拟内存空间,你可以像操作内存一样操作文件,避免了 read/write 的数据拷贝,大大提高了性能。

核心思想:

  1. 打开源文件和目标文件。
  2. 获取源文件的大小。
  3. 使用 mmap() 将源文件映射到内存。
  4. 使用 mmap() 为目标文件创建一个映射(通常先用 ftruncate() 调整目标文件大小)。
  5. 使用 memcpy() 将源内存映射区的内容拷贝到目标内存映射区。
  6. 使用 munmap() 解除映射。

完整代码示例 (copy_mmap.c)

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
void copy_file_mmap(const char *src_path, const char *dest_path) {
    int src_fd, dest_fd;
    struct stat src_stat;
    char *src_map = NULL, *dest_map = NULL;
    // 1. 打开源文件
    src_fd = open(src_path, O_RDONLY);
    if (src_fd == -1) {
        fprintf(stderr, "Error opening source file: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    // 2. 获取源文件大小
    if (fstat(src_fd, &src_stat) == -1) {
        fprintf(stderr, "Error getting source file size: %s\n", strerror(errno));
        close(src_fd);
        exit(EXIT_FAILURE);
    }
    // 3. 打开/创建目标文件
    dest_fd = open(dest_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (dest_fd == -1) {
        fprintf(stderr, "Error opening destination file: %s\n", strerror(errno));
        close(src_fd);
        exit(EXIT_FAILURE);
    }
    // 4. 调整目标文件大小与源文件一致
    if (ftruncate(dest_fd, src_stat.st_size) == -1) {
        fprintf(stderr, "Error resizing destination file: %s\n", strerror(errno));
        close(src_fd);
        close(dest_fd);
        exit(EXIT_FAILURE);
    }
    // 5. 映射源文件
    src_map = mmap(NULL, src_stat.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
    if (src_map == MAP_FAILED) {
        fprintf(stderr, "Error mapping source file: %s\n", strerror(errno));
        close(src_fd);
        close(dest_fd);
        exit(EXIT_FAILURE);
    }
    // 6. 映射目标文件
    dest_map = mmap(NULL, src_stat.st_size, PROT_WRITE, MAP_SHARED, dest_fd, 0);
    if (dest_map == MAP_FAILED) {
        fprintf(stderr, "Error mapping destination file: %s\n", strerror(errno));
        munmap(src_map, src_stat.st_size);
        close(src_fd);
        close(dest_fd);
        exit(EXIT_FAILURE);
    }
    // 7. 内存拷贝
    memcpy(dest_map, src_map, src_stat.st_size);
    // 8. 解除映射
    if (munmap(src_map, src_stat.st_size) == -1) {
        perror("munmap source");
    }
    if (munmap(dest_map, src_stat.st_size) == -1) {
        perror("munmap destination");
    }
    // 9. 关闭文件描述符
    close(src_fd);
    close(dest_fd);
    printf("File copied successfully using mmap.\n");
}
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
        return EXIT_FAILURE;
    }
    copy_file_mmap(argv[1], argv[2]);
    return EXIT_SUCCESS;
}

直接调用系统命令 system()

这是一种“取巧”的方法,如果你的 C 程序不需要关心拷贝过程的细节,可以直接调用 Linux 的 cp 命令。

核心思想:

使用 system() 函数执行一个字符串命令。

linux c语言拷贝文件
(图片来源网络,侵删)

代码示例 (copy_system.c)

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
        return EXIT_FAILURE;
    }
    char command[256];
    snprintf(command, sizeof(command), "cp %s %s", argv[1], argv[2]);
    printf("Executing command: %s\n", command);
    int ret = system(command);
    if (ret == -1) {
        perror("system() call failed");
    } else {
        printf("Command executed with exit status: %d\n", WEXITSTATUS(ret));
    }
    return (ret == -1 || WEXITSTATUS(ret) != 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}

缺点:

  • 效率低:需要创建一个新的 shell 进程来执行 cp 命令,开销很大。
  • 不可移植:依赖于 cp 命令的存在,在 Windows 上无法工作。
  • 控制力弱:无法在 C 代码中获取拷贝过程中的错误信息或进度。

总结与对比

方法 优点 缺点 适用场景
read/write 效率高,底层灵活,无额外库依赖 代码稍复杂,需要手动处理错误和缓冲 大文件拷贝、性能敏感场景、需要精确控制I/O
fread/fwrite 代码简洁,可移植性好,标准库自带 性能略低于系统调用(但有缓冲优化) 通用文件操作,小到中等文件拷贝,推荐首选
mmap 性能极高,适合超大文件,内存拷贝非常快 实现复杂,对文件大小有限制,映射区域需连续 超大文件拷贝,内存映射文件操作
system("cp") 实现最简单,一行代码搞定 效率极低,不可移植,控制力弱 快速实现,不关心性能和可移植性的脚本化任务

推荐选择:

  • 日常学习和通用开发:使用 fread/fwrite 方法,它在代码简洁性和性能之间取得了很好的平衡,是大多数情况下的最佳选择。
  • 追求极致性能:如果拷贝的文件非常大(比如几百MB到GB级别),使用 mmap 方法可以获得最佳性能。
  • 学习底层原理:从 read/write 开始,这是理解文件 I/O 的基础。
  • 快速实现:如果只是临时用一下,不关心性能,可以用 system("cp")
-- 展开阅读全文 --
头像
页相关阅读如何实现动态调用?
« 上一篇 03-13
dede首页尾部如何添加内容?
下一篇 » 03-13

相关文章

取消
微信二维码
支付宝二维码

目录[+]