引言 - 业务有点麻烦

    C 功能很强大, 同样书写起来会谨慎(拖泥带水). 不妨通过一个小问题来描述裹脚的 C

需求:
用 C 创建一个文件! 难点在于
1. 文件路径切割成 目录 + 文件名
2. 目录 多级创建
3. 跨平台
这个问题也挺适合做线下面试作业. 本文围绕下面几个重点讲述
1. 获取文件更新时间
2. 目录创建和文件删除
3. 配置自动更新
应对平台是 Winds cl 和 Linux gcc :) 在开始之前, 先介绍一些文件辅助基础函数, 构造了两个最初的原始的 mkdir 和 mtime 函数.
mkdir 在 shell 中用于构建目录,常很用. 对于 mtime 是 Linux 对于文件最后修改时间.
最初来自于下面文件详细信息结构中
struct stat {
unsigned long st_dev; /* Device. */
unsigned long st_ino; /* File serial number. */
unsigned int st_mode; /* File mode. */
unsigned int st_nlink; /* Link count. */
unsigned int st_uid; /* User ID of the file's owner. */
unsigned int st_gid; /* Group ID of the file's group. */
unsigned long st_rdev; /* Device number, if device. */
unsigned long __pad1;
long st_size; /* Size of file, in bytes. */
int st_blksize; /* Optimal block size for I/O. */
int __pad2;
long st_blocks; /* Number 512-byte blocks allocated. */
long st_atime; /* Time of last access. */
unsigned long st_atime_nsec;
long st_mtime; /* Time of last modification. */
unsigned long st_mtime_nsec;
long st_ctime; /* Time of last status change. */
unsigned long st_ctime_nsec;
unsigned int __unused4;
unsigned int __unused5;
};
在结构体中 st_atime, st_mtime, st_ctime 字段可以知道, Linux 文件有三个时间属性: 

1. mtime: 文件内容最后修改时间
2. ctime: 文件状态改变时间, 如权限, 属性被更改
3. atime: 文件内容被访问时间, 如 cat, less 等 在默认情况下, ls 显示出来的是该文件的 mtime, 即文件内容最后修改时间.
如果你需要查看另外两个时间, 可以使用 ls -l --time ctime 命令. 消化上面小知识, 加上下面先入为主的设计思路, 就构建了代码 head 部分
#ifndef _H_FILE
#define _H_FILE #include <stdio.h>
#include <stdlib.h>
#include <string.h> #ifdef __GNUC__ #include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h> //
// mkdir - 通用的单层目录创建函数宏, 等价于 mkdir path
// path : 目录路径加名称
// return : 0表示成功, -1表示失败, 失败原因都在 errno
//
#undef mkdir
#define mkdir(path) \
mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) //
// mtime - 得到文件最后修改时间
// path : 文件名称
// return : 返回时间戳, -1 表示错误
//
inline time_t mtime(const char * path) {
struct stat ss;
// 数据最后的修改时间
return stat(path, &ss) ? -1 : ss.st_mtime;
} #endif #ifdef _MSC_VER #include <io.h>
#include <direct.h>
#include <windows.h> // int access(const char * path, int mode /* 四个检测宏 */);
#ifndef F_OK
# define F_OK (0)
#endif
#ifndef X_OK
# define X_OK (1)
#endif
#ifndef W_OK
# define W_OK (2)
#endif
#ifndef R_OK
# define R_OK (4)
#endif inline time_t mtime(const char * path) {
WIN32_FILE_ATTRIBUTE_DATA wfad;
if (!GetFileAttributesEx(path, GetFileExInfoStandard, &wfad))
return -1;
// 基于 winds x64 sizeof(long) = 4
return *(time_t *)&wfad.ftLastWriteTime;
} #endif #endif
原始的 mkdir 仿照 Winds 上面的 mkdir 接口设计.
    _Check_return_ _CRT_NONSTDC_DEPRECATE(_mkdir)
_ACRTIMP int __cdecl mkdir(
_In_z_ char const* _Path
);
最终行为和 Linux 的 mkdir 创建目录命令一致(0665,即-rw-rw-r-x).
mtime 得到文件最后修改时间. 主要用于获取时间变化促使配置自动更新.
F_OK, X_OK, W_OK, R_OK 在 Winds 上实现 access 接口缺少的宏定义.
更加具体可以参照

file.h

前言 - 文件辅助操作

前言部分可能最精华, 开始步入正轨 ~
//
// mkdirs - 创建多级目录
// path : 目录路径
// return : < 0 is error, 0 is success
//
int
mkdirs(const char * path) {
char c, * p, * s; // 参数错误直接返回
if (!path || !*path) return -2;
// 文件存在 or 文件一次创建成功 直接返回
if (!access(path, F_OK) || !mkdir(path))
return 0; // 跳过第一个 ['/'|'\\'] 检查是否是多级目录
p = (char *)path;
while ((c = *++p) != '\0')
if (c == '/' || c == '\\')
break;
if (c == '\0') return -1; // 开始循环构建多级目录
s = p = strdup(path);
while ((c = *++p) != '\0') {
if (c == '/' || c == '\\') {
*p = '\0'; if (access(s, F_OK)) {
// 文件不存在, 开始创建, 创建失败直接返回错误
if (mkdir(s)) {
free(s);
return -1;
}
} *p = c;
}
} // 最后善尾
c = p[-1]; free(s);
if (c == '/' || c == '\\')
return 0; // 剩下最后文件路径, 开始构建
return mkdir(path) ? -1 : 0;
}
上面 mkdirs 一个具有实战意义的跨平台多级目录创建函数接口设计. 等同于 mkdir -p 操作.
核心思路在于
1. 直接 mkdir 成功就返回
2. 分级 切割 路径, 循环 mkdir 有了多级目录构建操作, 那顺路写个多级目录的删除操作.
//
// removes - 删除非空目录 or 文件
// path : 文件全路径
// return : < 0 is error, >=0 is success
//
inline int removes(const char * path) {
char s[BUFSIZ]; #ifndef STR_RMRF
# ifdef _MSC_VER
# define STR_RMRF "rmdir /s /q \"%s\""
# else
# define STR_RMRF "rm -rf '%s'"
# endif
#endif // path 超过缓冲区长度, 返回异常
if (snprintf(s, sizeof s, STR_RMRF, path) == sizeof s)
return -1;
return access(path, F_OK) ? 0 : -system(s);
}
如果你不希望存在输出内容在控制台上面可以使用这种类型操作
$ rm -rf '%s' >/dev/null 2>&1

or

> rmdir /s /q "%s" 1>>dev.nil 2>&1 ; del dev.nul
这里保留了输出内容, 方便日志采集发现问题.

正文 - 配置自动刷新

生活不止眼前的苟且, 很庆幸来到这里. 但感觉作用不大.
随后讲述的内容是如何构造一个动态刷新的配置系统. 整体的设计有如下考虑
1. 生命周期跟随系统. 所以只有 set 和 update
2. 线程安全
3. set null 等同于 update delete
可以通过代码来看这样的好处和细节. 首先看整体的数据结构设计
struct file {
time_t last; // 文件最后修改时间点
char * path; // 文件全路径
unsigned hash; // 文件路径 hash 值 file_f func; // 执行行为
void * arg; // 行为参数 struct file * next; // 文件下一个结点
}; static struct files {
atom_t lock; // 当前对象原子锁
struct file * head; // 当前文件对象集
} _s;
其中随后会用到原子锁操作, 更加详细的看下面库的设计.

atom.h

对于全局的 struct files _s 对象辅助函数 add 和 get 设计内涵见下面
// files add
static void _add(const char * p, unsigned h, file_f func, void * arg) {
struct file * fu;
if (mtime(p) == -1) {
RETNIL("mtime error p = %s", p);
} fu = malloc(sizeof(struct file));
fu->last = -1;
fu->path = strdup(p);
fu->hash = h;
fu->func = func;
fu->arg = arg; // 直接插入到头结点部分
atom_lock(_s.lock);
fu->next = _s.head;
_s.head = fu;
atom_unlock(_s.lock);
} // files get
static struct file * _get(const char * p, unsigned * r) {
struct file * fu = _s.head;
unsigned h = *r = str_hash(p); while (fu) {
if (fu->hash == h && strcmp(fu->path, p) == 0)
break;
fu = fu->next;
} return fu;
}
线程安全的辅助直白代码. 其中 str_hash 引用的如下函数
//
// str_hash - Brian Kernighan与 Dennis Ritchie 简便快捷的 hash算法
// str : 字符串内容
// return : 返回计算后的hash值
//
unsigned
str_hash(const char * str) {
register unsigned h = 0u;
if (str) {
register unsigned c;
while ((c = *str++))
h = h * 131u + c;
}
return h;
}
随后开始进入核心业务, 先是要看更新操作
//
// :0 一个和程序同生存的配置文件动态刷新机制
// file_f - 文件更新行为
//
typedef void (* file_f)(FILE * c, void * arg); //
// file_set - 文件注册触发行为
// path : 文件路径
// func : file update -> func(path -> FILE, arg), func is NULL 标记清除
// arg : 注入的额外参数
// return : void
//
void
file_set(const char * path, file_f func, void * arg) {
unsigned h;
assert(path && *path);
struct file * fu = _get(path, &h);
if (NULL == fu)
_add(path, h, func, arg);
else {
atom_lock(_s.lock);
fu->last = -1;
fu->func = func;
fu->arg = arg;
atom_unlock(_s.lock);
}
}
file_set 只负责在全局的 _s 对象中安全的插入数据, 其它业务什么都不管.

后面看详细的 file_update 操作
//
// file_update - 更新注册配置解析事件
// return : void
//
void
file_update(void) {
struct file * fu;
atom_lock(_s.lock); fu = _s.head;
while (fu) {
struct file * next = fu->next; if (NULL == fu->func) {
// 删除的是头结点
if (_s.head == fu)
_s.head = next; free(fu->path);
free(fu);
} else {
time_t last = mtime(fu->path);
if (fu->last != last && last != -1) {
FILE * c = fopen(fu->path, "rb+");
if (NULL == c) {
CERR("fopen rb+ error = %s.", fu->path);
continue;
}
fu->last = last;
fu->func(c, fu->arg);
fclose(c);
}
} fu = next;
}
atom_unlock(_s.lock);
}
file_update 前提也是线程安全的. 其次应对两个业务 delete 和 update.
其实从写过的角度而言. file_f 和 file_set 就已经决定了 file_update 具体设计了. 开始的时候 就已经决定了 能够达到的最好结尾 ~ (可能是代码写多了, 总感觉看 code 就够了, 说太多容易欲盖弥彰 :)

后记 - 也许是交作业

错误是难免的欢迎指正 ~ 我不哭我已经没有 ~ 尊严能放弃 ~

在人间

C中级 - 文件辅助操作的更多相关文章

  1. c json实战引擎四 , 最后❤跳跃

    引言  - 以前那些系列 长活短说, 写的最终 scjson 纯c跨平台引擎, 希望在合适场景中替代老的csjon引擎, 速度更快, 更轻巧. 下面也是算一个系列吧. 从cjson 中得到灵感, 外加 ...

  2. C 封装一个通用链表 和 一个简单字符串开发库

    引言 这里需要分享的是一个 简单字符串库和 链表的基库,代码也许用到特定技巧.有时候回想一下, 如果我读书的时候有人告诉我这些关于C开发的积淀, 那么会走的多直啊.刚参加工作的时候做桌面开发, 服务是 ...

  3. C 简单处理excel 转成 json

    引言 工作中常需要处理excel转json问题. 希望这篇博文能简单描述这个问题.并提供一种解决思路.提升感悟. 今天我们处理的事就是为了把 xlsm => json. 一种方式是. 去 goo ...

  4. .NET Core C# 中级篇2-7 文件操作

    .NET Core CSharp 中级篇2-7 本节内容为文件操作 简介 文件操作在我们C#里还是比较常见的,例如我们读取Excel.Txt文件的内容,在程序中,这些文件都是以流的方式读取进入我们内存 ...

  5. NET中级课--文件,流,序列化2

    1.流的类型体系: 基础流    装饰器流    包装器类    帮助类 2.               stream file~     memory~     network~ stream是个 ...

  6. NET中级课--文件,流,序列化1

    1.对于机器的角度来看,任何文件都是二进制的0和1. 2.   位:bit,一个1或0就是1位. 字节:byte,每8位一个字节.一个字节的范围就是00000000到1111111,换成10进制就是0 ...

  7. python全栈开发中级班全程笔记(第二模块)第一部分:文件处理

      第二模块 第一部分:文件处理与函数 #插曲之人丑就要多读书:读书能够提高个人素质与内涵,提升个人修养与能力,以及层次的提升. 推荐书籍:追风筝的人.白鹿原 电影:阿甘正传.辛德勒的名单 第一节:三 ...

  8. [ATL/WTL]_[中级]_[保存CBitmap到文件-保存屏幕内容到文件]

    场景: 1. 在做图片处理时,比方放大后或加特效后须要保存CBitmap(HBITMAP)到文件. 2.截取屏幕内容到文件时. 3.不须要增加第3方库时. 说明: 这段代码部分来自网上.第一次学atl ...

  9. 012-HQL中级2-Hive如何执行文件中的sql语句

    Hive可以运行保存在文件里面的一条或多条的语句,只要用-f参数,一般情况下,保存这些Hive查询语句的文件通常用.q或者.hql后缀名,但是这不是必须的,你也可以保存你想要的后缀名.假设test文件 ...

随机推荐

  1. windows下建立netcore控制台程序,然后传送到centos7下的docker容器里运行

    1.首先,在window下用vs2017开发netcore控制台项目. 2.把建立好的项目传送到centos7下面的容器里. docker cp sharefoldersforwindows/ 359 ...

  2. 微信网页IOS上传图片旋转解决方案

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  3. C++虚函数原理

    类中的成员函数分为静态成员函数和非静态成员函数,而非静态成员函数又分为普通函数和虚函数. Q: 为什么使用虚函数 A: 使用虚函数,我们可以获得良好的可扩展性.在一个设计比较好的面向对象程序中,大多数 ...

  4. 全局唯一ID生成器

    分布式环境中,如何保证生成的id是唯一不重复的? twitter,开源出了一个snowflake算法,现在很多企业都按照该算法作为参照,实现了自己的一套id生成器. 该算法的主要思路为: 刚好64位的 ...

  5. [控件] 心形加载的view

    心形加载的view 效果: 素材图片: 源码: StarView.h 与 StarView.m // // StarView.h // Star // // Created by XianMingYo ...

  6. 使用开源库 MagicalRecord 操作 CoreData

    MagicalRecord  https://github.com/magicalpanda/MagicalRecord 注意:  MagicalRecord 在 ARC 下运作,Core Data ...

  7. Linux 文件的读写执行权限的说明

    文件的读写执行权限的说明 X 进入目录的权限: cd 1.文件本身是可执行的 2.普通用户还具备r的权限 3.root用户只需要有r的权限即可 r 查看目录/文件的内容 :ls dir 没有读的权 限 ...

  8. 17 汽车服务工程 李腾飞 MP4

  9. CSS学习摘要-定位实例

    CSS学习摘要-定位实例 注:全文摘自MDN-CSS定位实例 列表消息盒子 我们研究的第一个例子是一个经典的选项卡消息框,你想用一块小区域包括大量信息时,一个非常常用的特征.这包括含有大信息量的应用, ...

  10. 原生js实现一个DIV的碰撞反弹运动

     原生js实现一个DIV的碰撞反弹运动: 关键在于DIV的边界检测,进而改变运动方向,即可实现碰撞反弹效果. <!DOCTYPE html> <html lang="en& ...