这节本想直接介绍I/O优化的,后来思考一下有必要对常用的I/O操作函数的特点介绍一下,这样要好些。下面就先介绍和I/O有关的库函数(以C99为准)
不同的操作系统有不同的文件管理方式,现行的主要有FAT(fileallocation table)、FAT32、NTFS(new technoly file system)、NWFS(netware file system)以及UFS(unix file system)当然还有一些分布式文件管理系统如:AFS(andrew filesystem)、DFS、Micorsoft DFS和NFS(net file system),这些文件系统的安全机制和属性都有着区别,所以当你做I/O优化时,要关注一下你所用的文件系统类型。
第一:对于一些格式化输入输出函数要注意检查format的类型是否匹配如fprintf(),printf(), sprintf(), snprintf(), vfprintf(), vprintf(), vsprintf()和vsnprintf()等。
第二:remove(),rename(),fopen(),freopen()这几个函数使用时要注意两点:a,要指定文件名和操作属性;b,这些操作可以改变文件属性,有的改动是不可逆的,一定要小心。如fopen的时候,remove就不能使用。多路并发的时候尤其要注意。关于多线程/多进程的风险控制,后面会有详细介绍,这里提到一下。
第三:文件路径推荐标准化处理,另外根据要求,选择相对路径和绝对路径。有很多公司都有封装好的canonicalize path name module,如果你是新人,问一下老员工。千万别等到软件发布时,才发现路径有问题。
第四:fopen使用的时候,要知道你是要创建一个文件还是打开一个已经存在的。有些人为了避免混淆,在fopen做了一层封装(wrapper),如创建文件用creat_file封装fopen的创建文件功能,对我自己而言,我不太喜欢用这种wrapper。当然这是个人习惯。总之你要知道自己的目的。
第五:文件操作函数,如果有返回值的一定要检查返回值,确定文件操作结果是你想要的。
使用error_no解析文件操作结果时,多线程时要注意控制粒度。具体解决方案,可以参考任何一本多线程书籍。或多或少都有提及怎么安全使用error_no。
第六:用fseek()替换rewind(),原因很简单rewind没有返回值,无法做安全性检查。fseek(stream, 0L, SEEK_SET)在功能上和rewind一样。
第七:remove,rename操作的时候,确定文件的状态是否是open。
第八:当代码对数据完整性有要求时,请用fflush()刷新缓冲区。
第九:用setvbuf代替setbuf ,同样理由是前者可以做安全性检查setbuf(stream, buf)==setvbuf(stream, buf, _IOFBF, BUFSIZ) or setvbuf(stream, buf, _IONBF, BUFSIZ)
第十:了解text mode和binary mode的区别。这两种模式下使用fseek和ungetc的方式是不同的。希望读者自己总结
第十一:习惯用foef和ferror来检查文件结尾和文件操作错误
第十二:getc()和putc()是宏,所以有边际效应。如char
*file_name;
FILE
*fptr;
/*Initialize file_name */
int
c = getc(fptr = fopen(file_name,"r"));
if
(c == EOF) {
/*Handle error */
}
//
文件被打开了两次。。所以会出错。应该这样写:
int
c;
char
*file_name;
FILE
*fptr;
/*Initialize file_name */
fptr= fopen(file_name, "r");
if
(fptr == NULL) {
/*Handle error */
}
c= getc(fptr);
if
(c == EOF) {
/*Handle error */
}
第十三:最后再强调一下,文件打开要关闭,返回类型要做安全检查。
关于C++流操作的话题,将会在后面的内容介绍,这里只介绍C的,也欢迎大家补充这方面的内容