【Linux 封神之路】文件操作 + 时间编程实战:从缓冲区到时间格式化全解析

【Linux 封神之路】文件操作 + 时间编程实战:从缓冲区到时间格式化全解析

大家好,我是专注 Linux 技术分享的小杨。上一篇给大家整理了系统监控与性能分析工具,解决了服务器卡慢、程序崩溃的问题。今天接着 Linux 开发核心技能系列,聚焦 "文件操作" 和 "时间编程"------ 这两个是嵌入式 Linux 开发的高频需求,比如日志写入、配置文件读写、时间戳记录等场景都离不开。结合文件操作及时间编程资料,从 "缓冲区 / 非缓冲区文件操作" 到 "时间格式转换",手把手教你实战用法,附完整代码示例,新手直接套用!

一、先理清:文件操作的两种核心方式

Linux 文件操作分为 "基于缓冲区" 和 "基于非缓冲区" 两种,适用场景不同,核心区别在于是否通过标准库缓冲区优化 IO 效率:

  • 基于缓冲区(fopen/fwrite/fread):标准库封装,自动缓冲数据,减少系统调用次数,适合普通文件读写(如文本文件、配置文件);
  • 基于非缓冲区(open/write/read):直接调用系统调用,无中间缓冲,实时性高,适合设备文件操作(如串口、磁盘)、嵌入式实时场景。

下面分别拆解两种方式的核心函数和实战代码。

二、实战 1:基于缓冲区的文件操作(标准库函数)

基于缓冲区的文件操作通过<stdio.h>头文件的函数实现,用法简单、兼容性好,是开发中的首选。

1. 核心函数详解(按操作流程)

函数 功能 核心参数 / 返回值
fopen 打开 / 创建文件 参数 1:文件路径(如"test.txt");参数 2:打开模式("r"读、"w"写、"a"追加、"r+"读写);返回值:文件指针(FILE *),失败返回NULL
fwrite 写入数据 参数 1:数据缓冲区地址;参数 2:单个数据大小;参数 3:数据个数;参数 4:文件指针;返回值:成功写入的个数
fprintf 格式化写入(文本文件) 类似printf,多一个文件指针参数(如fprintf(fp, "name:%s\n", "Linux")
fread 读取数据 参数 1:接收数据的缓冲区;参数 2:单个数据大小;参数 3:读取个数;参数 4:文件指针;返回值:成功读取的个数
fscanf 格式化读取(文本文件) 类似scanf,多一个文件指针参数(如fscanf(fp, "%d", &num)
fseek 移动文件光标 参数 1:文件指针;参数 2:偏移量(正值向右、负值向左);参数 3:基准位置(SEEK_SET文件开头、SEEK_CUR当前位置、SEEK_END文件末尾)
fclose 关闭文件 参数:文件指针;返回值:0 成功,-1 失败(必须关闭,避免资源泄漏)

2. 实战代码:文本文件读写(日志记录场景)

c

运行

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

int main() {
    // 1. 打开文件(不存在则创建,存在则追加写入)
    FILE *fp = fopen("app.log", "a+");
    if (fp == NULL) {
        perror("fopen failed");
        return 1;
    }

    // 2. 格式化写入数据(模拟日志:时间+内容)
    char log_msg[] = "2026-01-28 16:00:00 [INFO] 程序启动成功\n";
    fprintf(fp, "%s", log_msg);

    // 3. 移动光标到文件开头,读取所有日志
    fseek(fp, 0, SEEK_SET);
    char buf[1024];
    printf("日志内容:\n");
    while (fgets(buf, sizeof(buf), fp) != NULL) { // 逐行读取
        printf("%s", buf);
    }

    // 4. 关闭文件
    fclose(fp);
    fp = NULL; // 避免野指针
    return 0;
}

3. 关键注意事项

  • 打开模式选择:"w"会清空文件原有内容,"a"会在文件末尾追加,"r+"支持读写但不会创建文件;
  • 必须检查fopen返回值:文件不存在、权限不足都会导致打开失败;
  • 写完数据后若需立即读取,需用fseek移动光标(否则光标在文件末尾,读取不到数据)。

三、实战 2:基于非缓冲区的文件操作(系统调用)

非缓冲区文件操作通过系统调用实现,无中间缓冲,IO 响应更快,适合嵌入式实时场景(如串口通信、磁盘 IO)。

1. 核心函数详解(按操作流程)

函数 功能 核心参数 / 返回值
open 打开 / 创建文件 参数 1:文件路径;参数 2:打开标志(必选:O_RDONLY只读、O_WRONLY只写、O_RDWR读写;可选:O_CREAT创建、O_APPEND追加、O_TRUNC清空);参数 3:文件权限(O_CREAT时必填,如0644=rw-r--r--);返回值:文件描述符(int),失败返回 - 1
write 写入数据 参数 1:文件描述符(open 返回值);参数 2:数据缓冲区;参数 3:数据长度;返回值:成功写入的字节数,失败返回 - 1
read 读取数据 参数 1:文件描述符;参数 2:接收缓冲区;参数 3:读取长度;返回值:成功读取的字节数(0 表示文件结束),失败返回 - 1
lseek 移动光标 参数 1:文件描述符;参数 2:偏移量;参数 3:基准位置(SEEK_SET/SEEK_CUR/SEEK_END);返回值:光标位置的字节数,失败返回 - 1
close 关闭文件 参数:文件描述符;返回值:0 成功,-1 失败

2. 实战代码:二进制文件读写(数据存储场景)

c

运行

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

// 定义要存储的结构体(模拟传感器数据)
typedef struct {
    int temp;    // 温度
    int humidity;// 湿度
    long time;   // 时间戳
} SensorData;

int main() {
    // 1. 打开文件(创建+读写+清空原有内容,权限0644)
    int fd = open("sensor.bin", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    // 2. 准备数据并写入(二进制格式)
    SensorData data = {25, 60, 1738064000};
    ssize_t write_len = write(fd, &data, sizeof(SensorData));
    if (write_len != sizeof(SensorData)) {
        perror("write failed");
        close(fd);
        return 1;
    }
    printf("写入二进制数据成功,长度:%ld字节\n", write_len);

    // 3. 移动光标到文件开头,读取数据
    lseek(fd, 0, SEEK_SET);
    SensorData read_data;
    ssize_t read_len = read(fd, &read_data, sizeof(SensorData));
    if (read_len == -1) {
        perror("read failed");
        close(fd);
        return 1;
    }
    printf("读取数据:温度=%d℃,湿度=%d%%,时间戳=%ld\n", 
           read_data.temp, read_data.humidity, read_data.time);

    // 4. 关闭文件
    close(fd);
    return 0;
}

3. 关键注意事项

  • 文件权限:0644表示所有者可读可写,组用户和其他用户只读(嵌入式开发中常用权限);
  • 文件描述符:open返回的是整数(如 3、4),而非FILE *,需注意与缓冲区操作的区别;
  • 无缓冲特性:write调用后数据直接写入文件(或设备),无需刷新缓冲区,实时性更高。

四、实战 3:时间编程(时间戳 + 格式化转换)

时间编程在日志记录、数据同步、定时任务中必不可少,核心是 "时间戳→时间结构体→格式化字符串" 的转换流程。

1. 核心概念与结构体

  • 时间戳:从 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC)到当前时间的秒数(time_t类型);
  • 时间结构体(struct tm):拆分后的时间格式,包含秒、分、时、日、月、年等字段(注意:tm_mon从 0 开始,tm_year= 实际年份 - 1900)。

2. 核心函数详解(按转换流程)

函数 功能 核心参数 / 返回值
time 获取当前时间戳 参数:保存时间戳的地址(可传NULL直接返回);返回值:当前时间戳,失败返回 - 1
localtime 时间戳→本地时间结构体 参数:时间戳指针;返回值:struct tm *(本地时间,如北京时间)
gmtime 时间戳→格林威治时间结构体 参数:时间戳指针;返回值:struct tm *(UTC 时间,比北京时间晚 8 小时)
ctime 时间戳→默认格式字符串 参数:时间戳指针;返回值:字符串指针(如"Wed Jan 28 16:30:00 2026\n"
asctime 时间结构体→默认格式字符串 参数:struct tm *;返回值:字符串指针(格式同ctime
strftime 时间结构体→自定义格式字符串 参数 1:结果缓冲区;参数 2:缓冲区大小;参数 3:格式字符串(如"%Y-%m-%d %H:%M:%S");参数 4:时间结构体;返回值:成功转换的字符数

3. 实战代码:时间格式化(日志时间戳场景)

c

运行

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

int main() {
    // 1. 获取当前时间戳
    time_t now = time(NULL);
    if (now == -1) {
        perror("time failed");
        return 1;
    }
    printf("当前时间戳:%ld\n", now);

    // 2. 时间戳→本地时间结构体
    struct tm *local_tm = localtime(&now);
    if (local_tm == NULL) {
        perror("localtime failed");
        return 1;
    }

    // 3. 自定义格式化时间(常用格式:年-月-日 时:分:秒)
    char time_str[64];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", local_tm);
    printf("自定义格式时间:%s\n", time_str);

    // 4. 时间戳→默认格式字符串
    char *default_time = ctime(&now);
    printf("默认格式时间:%s", default_time);

    // 5. 时间结构体→默认格式字符串
    char *asctime_str = asctime(local_tm);
    printf("asctime格式时间:%s", asctime_str);

    return 0;
}

4. 常用时间格式符(strftime核心)

格式符 含义 示例
%Y 4 位年份 2026
%m 2 位月份(01-12) 01
%d 2 位日期(01-31) 28
%H 24 小时制小时(00-23) 16
%M 分钟(00-59) 30
%S 秒(00-59) 45
%w 星期(0-6,0 为周日) 3
%j 一年中的第几天(001-366) 028

五、综合实战:文件操作 + 时间编程(日志系统)

结合前面的知识点,实现一个带时间戳的日志系统,自动记录程序运行状态:

c

运行

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

// 日志写入函数(带时间戳)
void write_log(const char *level, const char *msg) {
    // 1. 打开日志文件(追加模式)
    FILE *fp = fopen("system.log", "a");
    if (fp == NULL) {
        perror("fopen log failed");
        return;
    }

    // 2. 获取当前时间并格式化
    time_t now = time(NULL);
    struct tm *local_tm = localtime(&now);
    char time_str[64];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", local_tm);

    // 3. 写入日志(格式:时间 级别 消息)
    fprintf(fp, "[%s] [%s] %s\n", time_str, level, msg);

    // 4. 关闭文件
    fclose(fp);
    fp = NULL;
}

int main() {
    write_log("INFO", "程序启动");
    write_log("INFO", "开始执行数据采集");
    write_log("WARNING", "传感器数据波动较大");
    write_log("INFO", "程序执行完成");

    printf("日志写入成功,查看system.log文件\n");
    return 0;
}

运行后查看system.log文件,输出如下:

plaintext

复制代码
[2026-01-28 17:00:00] [INFO] 程序启动
[2026-01-28 17:00:01] [INFO] 开始执行数据采集
[2026-01-28 17:00:05] [WARNING] 传感器数据波动较大
[2026-01-28 17:00:10] [INFO] 程序执行完成

六、避坑指南:常见错误解决

  1. 文件操作失败(权限不足)

    • 原因:创建文件时未指定权限(openO_CREAT但未传mode参数),或目录无写权限;
    • 解决:open函数添加权限参数(如0644),或用sudo运行程序(仅测试场景)。
  2. 时间格式化出现乱码

    • 原因:strftime的缓冲区大小不足,或格式符错误;
    • 解决:增大缓冲区(如char time_str[64]),核对格式符(如%Y而非%y)。
  3. 非缓冲区操作读取不到数据

    • 原因:read后未判断返回值,或光标位置错误;
    • 解决:用lseek移动光标到正确位置,检查read返回值(0 表示文件结束,-1 表示失败)。

七、总结:核心技能与应用场景

  1. 文件操作:
    • 普通文本 / 配置文件:用缓冲区操作(fopen/fprintf/fscanf),效率更高;
    • 设备文件 / 实时场景:用非缓冲区操作(open/write/read),实时性更强;
  2. 时间编程:
    • 日志记录:用strftime自定义时间格式,搭配文件操作实现带时间戳日志;
    • 数据同步:用time获取时间戳,实现跨设备时间同步;
  3. 嵌入式适配:
    • 权限控制:嵌入式设备中文件权限需严格配置(如0644),避免权限过高;
    • 资源释放:文件操作后必须关闭(fclose/close),避免资源泄漏。

掌握文件操作和时间编程,能应对嵌入式开发中 80% 的 IO 场景(日志、配置、数据存储)。下一篇博客,我会给大家整理 "Linux 多线程编程",解决并发执行、资源同步等问题,敬请关注!

相关推荐
数据知道3 小时前
PostgreSQL核心原理:一文掌握Postmaster与子进程的协作机制
数据库·postgresql
xqqxqxxq3 小时前
结构体(Java 类)实战题解笔记(持续更新)
java·笔记·算法
wifi chicken4 小时前
Linux wlan 之sniffer log 解密详解
linux·wlan·sniffer log·空口包·空口解密
济6174 小时前
ARM Linux 驱动开发篇----字符设备驱动开发(1)--字符设备驱动简介---- Ubuntu20.04
linux·嵌入式硬件
会开花的二叉树4 小时前
高性能定时器:时间轮算法的工程实践
算法
大江东去浪淘尽千古风流人物4 小时前
【LingBot-Depth】Masked Depth Modeling for Spatial Perception
人工智能·算法·机器学习·概率论
浪客灿心4 小时前
Linux的Ext系列文件系统
linux·运维·服务器·c语言
RFCEO4 小时前
学习前端编程:精准选中 HTML 元素的技巧与方法
前端·学习·css类选择器·兄弟元素选中·父子选中·关系选中·选择器选中
Ronaldinho Gaúch4 小时前
leetcode279完全平方数
c++·算法·动态规划