一.文件I/O和标准I/O区别
先来了解下什么是文件I/O和标准I/O:
文件I/O:文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。
标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准I/O库处理很多细节。例如缓存分配,以优化长度执行I/O等。标准的I/O提供了三种类型的缓存。
(1) 全缓存:当填满标准I/O缓存后才进行实际的I/O操作。
(2) 行缓存:当输入或输出中遇到新行符时,标准I/O库执行I/O操作。
(3) 不带缓存:stderr就是了。
文件I/O 又称为低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。标准I/O被称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是GLIBC,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。
通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。
文件I/O中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准I/O中用FILE(流)表示一个打开的文件,通常只用来访问普通文件
二.文件I/O函数介绍
1.open函数
open函数:调用它可以打开或者创建一个文件。
#include<sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags,mode_t mode);
如果失败,返回值为-1
参数解析:
pathname是要打开或者创建的文件名。flags 文件打开时候的选项, O_RDONLY以只读方式打开文件。O_WRONLY以只写方式打开文件。 O_RDWR以读、写方式打开文件。
O_EXEC只执行打开,O_SEARCH只搜索打开,以上五个必须必须指定一个且只能指定一个
flags 可选选项:
O_APPEND 以追加方式打开文件,每次写时都写在文件末尾。
O_CREAT 如果文件不存在,则创建一个,存在则打开它。
O_EXCL 与O_CREAT一起使用时,如果文件已经存在则返回出错。
O_TRUNC 以只写或读写方式打开时,把文件截断为0
O_DSYNC 每次write时,等待数据写到磁盘上。
O_RSYNC 每次读时,等待相同部分先写到磁盘上。
O_SYNC 每次write时,等到数据写到磁盘上并接更新文件属性。
SYNC选项都会影响降低性能,有时候也取决于文件系统的实现。
mode 只有创建文件时才使用此参数,指定文件的访问权限。模式有:
S_IRWX[UGO] 可读 可写 可执行
S_IR[USR GRP OTH] 可读
S_IW[USR GRP OTH] 可写
S_IX[USR GRP OTH] 可执行
S_ISUID 设置用户ID
S_ISGID 设置组ID
U->user G->group O->others
2.creat函数
creat 以只写方式创建一个文件,若文件已经存在,则把它截断为0
#include<sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_tmode);
参数解析:
pathname 要创建的文件名称mode 跟open的第三个参数相同,可读,可写,可执行 。如果失败 ,返回值为-1
creat函数等同于 open (pathname, O_WRONLY | O_CREAT |O_TRUNC, mode)
3.close函数
close 关闭已经打开的文件,并释放文件描述符
#include <unistd.h>
int close(int fd);
参数解析:filedes 文件描述符,有open或者creat返回的非负整数。
如果失败,返回值为-1
当一个进程结束时,操作系统会自动释放该进程打开的所有文件。但还是推荐用close来关闭文件。
lsof命令可以查看进程打开了那些文件。
4.lseek函数
lseek 用来定位当前文件偏移量,既你对文件操作从文件的那一部分开始。
#include<sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, intwhence);
如果失败,返回值为-1,成功返回移动后的文件偏移量。
参数解析:filedes 文件描述符。offset 必须与whence一同解析
whence为 SEEK_SET, 则offset从文件的开头算起。
whence为 SEEK_CUR, 则offset从当前位置算起,既新偏移量为当前偏移量加上offset
whence为 SEEK_END, 则offset从文件末尾算起。
可以通过lseek、write来快速创建一个大文件。
5.read函数
read 从当前文件偏移量处读入指定大小的文件内容
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
失败返回-1, 成功返回读入的字节数,到文件末尾返回0
参数解析 filedes 文件描述符 ,有open返回。buf 读入文件内容存放的内存首地址。nbytes 要读取的字节数。
实际读入的字节数可能会小于要求读入的字节数。比如文件只有所剩的字节数小于你要读入的字节数,读取fifo文件和网络套接字时都可能出现这种情况。
6.write函数
write向一个文件写入一定字节的内容。
#include <unistd.h>
ssize_t write(int fd, const void *buf,size_t count);
失败返回-1,成功返回实际写入的字节数。当磁盘满或者文件到达上限时可能写入失败。
一般从当前文件偏移量出写入,但如果打开时使用了O_APPEND,那么无论当前文件偏移量在哪里,都会移动到文件末尾写入。
案例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define BTS_MAGIC_NUM 4
int main()
{
intfd1,fd2;
charbts_magc[BTS_MAGIC_NUM];
intread_count;
intwrite_count;
/*1 open file */
fd1= open("TI.bts",O_RDWR|O_CREAT|O_EXCL,0666);
if(-1== fd1)
{
printf("fileexit\n");
fd1= open("TI.bts",O_RDWR);
if(-1== fd1)
{
printf("openfd 1error:%m\n");
}
}
fd2= open("TIInit_7.2.31.bts",O_RDWR,0666);
if(-1== fd2)
{
printf("openfd2 error:%m\n");
}
/*2 read file */
read_count= read(fd2,bts_magc,BTS_MAGIC_NUM);
if(BTS_MAGIC_NUM!= read_count)
{
printf("readerror %d\n",read_count);
}
/*3 write file */
write_count= write(fd1,bts_magc,BTS_MAGIC_NUM);
if(BTS_MAGIC_NUM!= write_count)
{
printf("writeerror %d\n",write_count);
}
/*4 close file */
close(fd1);
close(fd2);
}
整个实现的效果是:当前目录中有一个文件TIInit_7.2.31.bts,读取前四个字节,放到TI.bts这个文件中
三.标准I/O函数介绍
1.fopen/fclose函数
在操作文件之前要用fopen打开文件,操作完毕要用fclose关闭文件。打开文件就是在操作系统中分配一些资源用于保存该文件的状态信息,并得到该文件的标识,以后用户程序就可以用这个标识对文件做各种操作,关闭文件则释放文件在操作系统中占用的资源,使文件的标识失效,用户程序就无法再操作这个文件了。
#include <stdio.h>
FILE *fopen(const char *path, const char*mode);
int fclose(FILE *fp);
mode参数是一个字符串,由rwatb+六个字符组合而成,r表示读,w表示写,a表示追加(Append),在文件末尾追加数据使文件的尺寸增大。t表示文本文件,b表示二进制文件,有些操作系统的文本文件和二进制文件格式不同,而在UNIX系统中,无论文本文件还是二进制文件都是由一串字节组成,t和b没有区分,用哪个都一样,也可以省略不写。如果省略t和b,rwa+四个字符有以下6种合法的组合:
"r"
只读,文件必须已存在
"w"
只写,如果文件不存在则创建,如果文件已存在则把文件长度截断(Truncate)为0字节再重新写,也就是替换掉原来的文件内容
"a"
只能在文件末尾追加数据,如果文件不存在则创建
"r+"
允许读和写,文件必须已存在
"w+"
允许读和写,如果文件不存在则创建,如果文件已存在则把文件长度截断为0字节再重新写
"a+"
允许读和追加数据,如果文件不存在则创建
2.fread/fwrite函数
#include <stdio.h>
size_t fread(void *ptr, size_tsize, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr,size_t size, size_t nmemb,
FILE *stream);
比部分要注意的是返回值不是写的或者读取的字符数,借用man的话是:
fread() and fwrite() return the number ofitems successfully read orwritten (i.e., not the number of characters). If an error occurs, orthe end-of-file is reached, the return value is ashort item count (orzero).
3.fseek函数
#include <stdio.h>
int fseek(FILE *stream, longoffset, int whence);
对应文件i/o的lseek
fread和fwrite用于读写记录,这里的记录是指一串固定长度的字节,比如一个int、一个结构体或者一个定长数组。参数size指出一条记录的长度,而nmemb指出要读或写多少条记录,这些记录在ptr所指的内存空间中连续存放,共占size * nmemb个字节,fread从文件stream中读出size * nmemb个字节保存到ptr中,而fwrite把ptr中的size * nmemb个字节写到文件stream中。
nmemb是请求读或写的记录数,fread和fwrite返回的记录数有可能小于nmemb指定的记录数。例如当前读写位置距文件末尾只有一条记录的长度,调用fread时指定nmemb为2,则返回值为1。如果当前读写位置已经在文件末尾了,或者读文件时出错了,则fread返回0。如果写文件时出错了,则fwrite的返回值小于nmemb指定的值。下面的例子由两个程序组成,一个程序把结构体保存到文件中,另一个程序和从文件中读出结构体。
实例:
#include<stdio.h>
#defineBTS_MAGIC_NUM 4
int main()
{
FILE *fp1;
FILE *fp2;
char bts_magc[BTS_MAGIC_NUM];
int read_count;
int write_count;
/* 1 fopen file */
fp1 =fopen("TI.bts","w");
if(fp1 == NULL)
{
printf("fopen 1fault\n");
}
fp2 =fopen("TIInit_7.2.31.bts","r");
if(fp2 == NULL)
{
printf("fopen 2fault\n");
}
/* 2 read file */
read_count =fread(bts_magc,BTS_MAGIC_NUM,1,fp2);
/* 3 write file */
write_count =fwrite(bts_magc,BTS_MAGIC_NUM,1,fp1);
/* 4 close file */
fclose(fp1);
fclose(fp2);
}