news 2026/7/29 18:06:25

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【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核心)

格式符含义示例
%Y4 位年份2026
%m2 位月份(01-12)01
%d2 位日期(01-31)28
%H24 小时制小时(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 多线程编程”,解决并发执行、资源同步等问题,敬请关注!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/28 9:15:25

AI历史学家偏见:算法重构事件的客观性质质疑——软件测试从业者的技术应对与伦理责任

一、算法重构历史的偏见生成机制 数据层面的结构性偏差 AI历史模型依赖的训练数据常包含隐性偏见。如殖民史料的种族视角缺失、女性历史记录的系统性忽略等&#xff0c;导致算法将历史人物事件进行片面化建模。2025年NJU研究表明&#xff0c;未清洗的史料库中78%存在地域与性别…

作者头像 李华
网站建设 2026/7/28 21:14:56

极速构建数据应用:Streamlit 入门与实战全指南

极速构建数据应用&#xff1a;Streamlit 入门与实战全指南 引言 在数据驱动的时代&#xff0c;我们常常面临一个困境&#xff1a;费尽心力完成了一个数据分析或训练出一个不错的机器学习模型&#xff0c;却难以向同事、领导或客户直观地展示成果。传统的 Web 应用开发需要前端…

作者头像 李华
网站建设 2026/7/20 14:06:23

AI教材编写必备!低查重工具助力,轻松生成优质教材!

以下是将你提供的 HTML 代码转换为标准 Markdown 格式的内容&#xff1a; 教材适配难题与 AI 工具的解决方案 编写教材&#xff0c;怎样才能有效满足多样的需求&#xff1f;不同年级的学生在认知上的差异非常显著&#xff0c;内容如果过于复杂或者简单都不理想&#xff1b;而…

作者头像 李华
网站建设 2026/7/22 19:12:46

OpenPLC Runtime v4 架构(英译中)

OpenPLC Runtime v4 架构 概述 OpenPLC Runtime v4 是一个双进程系统,通过 REST API 服务器(用于 OpenPLC Editor 通信)和实时 PLC 执行引擎提供工业自动化能力。 系统组件 1. REST API 服务器进程 (Python/Flask) REST API 服务器是一个基于 Flask 的 HTTPS 应用程序,…

作者头像 李华
网站建设 2026/7/1 14:58:18

智瞳·工业装备健康透视—基于谱幅自适应融合的退化评估智能算法(Python)

算法特点1. 多频带谱幅融合创新改进传统单频带健康指数的局限性&#xff0c;实现全频段谱幅信息的自适应加权融合通过优化权重分配&#xff0c;实现关键故障频带的自动识别与增强2. 多目标协同优化将可分性、单调性、稀疏性三个关键性能指标统一到优化框架中提出信噪比优化模型…

作者头像 李华