Linux基础知识 - 文件IO操作相关系统编程
文件IO操作相关系统编程
这里主要说两套IO操作接口,分别是:
POSIX标准
read|write接口,函数定义在#include<unistd.h>
ISO C标准
fread|fwrite接口,函数定义在#include<stdio.h>
有书上说POSIX标准与ISO C标准的区别在于文件读写是否带缓冲区,我则不是很认同,因此POSIX标准下的IO操作也是带缓冲区的,至于这两个标准下的IO性能谁更加好则不一定,因为这和缓冲区的大小,以及用户逻辑有很大关系。
POSIX标准
ssize_t read (int __fd, void *__buf, size_t __nbytes)
ssize_t write (int __fd, constvoid *__buf, size_t __n)
读规则:
如预读字节数>文件总字节数,则全部读入文件字节数,返回值为文件字节数
如预读字节数<文件总字节数,则读满__buf(以__nbytes为准)后返回,下回读取会将继续读
如读到文件末尾,则返回值为0
比如:文件长度是100,buf长度是70,那么第一个读取70,读2此会读取剩下的30 ,第三次由于文件游标已经处于文件末尾,则返回0
写操作
#include <unistd.h>
#include <fcntl.h>
#include<stdio.h>
#define BUFFER_SIZE 200
int main() {
int fd = -1;
if (access("/tmp/iofile", F_OK)) {
fd = creat("/tmp/iofile", 0777);
} else {
fd = open("/tmp/iofile", O_WRONLY | O_APPEND);
}
if (fd == -1) {
perror("文件打开错误!");
return -1;
}
char buf[BUFFER_SIZE];
int val = 0, sum = 0;
do {
val = read(0, buf, BUFFER_SIZE);
if (val > 0) {
write(fd, buf, BUFFER_SIZE);
} else {
break;
}
sum += val;
} while (1);
printf("写入数据总长度是:%d\n", sum);
return 1;
}
读操作
#include <unistd.h>
#include <fcntl.h>
#include<stdio.h>
#define BUFFER_SIZE 400
int main() {
int fd = open("/tmp/iofile", O_RDONLY);
if (fd == -1) {
perror("文件打开错误!");
return -1;
}
char buf[BUFFER_SIZE];
int val = 0, sum = 0;
do {
val = read(fd, buf, BUFFER_SIZE);
printf("读入数据长度是:%d\n", val);
if (val > 0) {
write(1, buf, BUFFER_SIZE);
printf("\n");
} else {
sleep(1);
}
sum += val;
} while (1);
return 1;
}
执行顺序
1.执行写操作:
tkf@tkf:~/workspace/FileIOWrite/Debug$./FileIOWrite </etc/mtab
写入数据总长度是:990
2.在另外命令行(进程)执行读操作
tkf@tkf:~/workspace/FiloIORead/Debug$./FiloIORead
读入数据长度是:400
读入数据长度是:400
读入数据长度是:200
读入数据长度是:0
……..
è由于此时文件游标已经处于文件末端因此,长度是0
读入数据长度是:0
3.再次执行写操作
tkf@tkf:~/workspace/FileIOWrite/Debug$./FileIOWrite </etc/mtab
写入数据总长度是:990
此时读端进程输出
读入数据长度是:400
读入数据长度是:400
读入数据长度是:200
读入数据长度是:0
è因为再次有数据写入,所以可以读到数据,当数据再次读取完毕,则返回0
当然对于第三步骤,我们也可以通过更改读进程游标的位置(lseek)使其能读到数据
IO效率
根据书上效率对比试验,当缓冲区大小(buf)等于文件系统块大小时,性能是最佳的。
文件系统块大小struct stat –>st_blksize 查看
对于IO操作主要步骤可以理解为:
1.内核与系统缓冲区的数据拷贝
2.系统缓冲区与用户缓冲区的拷贝
举例,用户BUF是10字节,系统缓冲区时4096字节,那么到我们写端将用户BUF数据拷贝被系统缓冲区中,由于系统缓冲区没有填满,因此不会执行IO操作,直到写满或者执行同步操作。对于读来说,文件系统会将数据先都预读到系统缓冲区,每次我们请求读都是从系统缓冲区拷贝到用户缓冲器。因此在数据存在缓冲区并没有写到磁盘时如果系统出现故障可能数据会丢失。
ISO C标准(标准IO)
标准IO是围绕流的,他与POSIX标准相比可以使用户不用关注分配缓冲区的大小,他会选择适当缓冲区以优化执行IO
冲洗(fflush)
对于标准IO来说,冲洗就是讲缓冲区的数据写入磁盘
缓冲
对于标准IO库提供了三种类型的缓冲
全缓冲:在填满标准IO缓冲区后才进行实际的IO操作
行缓冲:当输入和输出遇到换行符时才执行实际的IO操作
不带缓冲:每次一个都进行实际的IO操作
void setbuf(FILE *__restrict __stream, char *__restrict __buf) ;
int setvbuf (FILE *__restrict __stream, char *__restrict __buf,
int __modes, size_t __n) ;
参数:
__n:缓冲区长度
__buf:缓冲区指针
__stream文件流
__modes:缓冲模式
_IOFBF0:全缓冲
_IOLBF 1:行缓冲
_IONBF 2:无缓冲
函数
mode
buf
缓冲区长度
缓冲类型
setbuf
非空
长度为BUFSIZ的BUF
全缓冲,行缓冲
NULL
无缓冲区
不带缓冲
setvbuf
IOFBF
非空
长度为size的buf
全缓冲
NULL
合适长度的系统缓冲区
IOLBF
非空
长度为size的buf
行缓冲
NULL
合适长度的系统缓冲区
IONBF
无缓冲区
不带缓冲
#include <stdio.h>
#include <stddef.h>
#include <string.h>
int main() {
FILE * iostream = fopen("fopenfile", "w+");
if (iostream == NULL) {
perror("流打開錯誤");
return -1;
}
setvbuf(iostream, NULL, _IOFBF, BUFSIZ); //1
char *buf = "abcde"; //2
int size = fwrite(buf, sizeof(char), strlen(buf)+1 ,iostream);
printf("寫入的數據是:%s", buf);
fflush(iostream); //3
sleep(-1);
return 1;
}
针对上述代码做如下分析:
将3处进行注释,并执行
fopenfile文件无任何内容,因此现在数据都在缓冲区,由于进程SLEEP流未关闭,并且缓冲区也没有写满,因此不会执行IO操作
不注释任何内容,并执行
fopenfile文件内容为abcde,由于fflush冲洗将缓冲区数据写入文件
将1处缓冲模式改为_IOLBF,注释3处,并执行
fopenfile文件无任何内容,虽然指定了行缓冲但是没有行结束符,因此数据在缓冲区内,没有进行IO操作
将1处缓冲模式改为_IOLBF,注释3处,并将2处数据改为buf=”abcde\n” ,执行
fopenfile文件内容为abcde,由于设置行行缓冲,并且存在结束符,因此进行了IO操作
将1处缓冲模式改为_IONBF,注释3处,并执行
fopenfile文件内容为abcde,由于设置无缓冲,因此每次写入都会进行IO操作
主要函数
打开关闭流
打开一个指定的文件
FILE *fopen (constchar *__restrict __filename,
constchar *__restrict __modes)
通过文件描述符打开一个指定的文件
FILE *fdopen (int __fd, constchar *__modes)
modes:打开模式
R或rb
为读而打开
W或wb
把文件截断为0长,或为写而创建
A或ab
添加;在文件尾为写而打开,或为写而创建
R+
为读和写而打开
W+
把文件截断为0长,或为为读和写而打开
A+
为在文件尾为写而打开或创建
关闭文件流
intfclose (FILE *__stream);
单字符读写
读函数
int fgetc (FILE *__stream);
int getc (FILE *__stream);
int getchar (void);
fgetc是一个函数,getc可实现为宏,getchar为从标准输出流中获取一个字符,相当于getc(stdin)
返回值:若成功则返回读取到的字符,若失败或读到文件尾则返回-1
由于无论失败或者读到文件尾都返回-1,为了区分哪种原因返回的-1。提供下面2个函数
以读到文件结尾返回
int feof (FILE *__stream)
以产生错误返回返回
int ferror (FILE *__stream)
返回值:条件为真返回非0.条件为假返回0
写函数
int fputc(int __c, FILE *__stream);
int putc (int __c, FILE *__stream);
int putchar (int __c);
返回值:成功返回c,失败返回EOF
举例:
#include <stdio.h>
#include <stddef.h>
#include <string.h>
int main() {
FILE * iostream = fopen("fopenfile", "r");
if (iostream == NULL) {
perror("流打開錯誤");
return -1;
}
char c;
while ((c = getc(iostream)) != -1) {
printf("读取的字符是:");
putchar(c);
printf("\n");
}
if (feof(iostream)) {
printf("读取到文件末尾结束\n");
}
if (ferror(iostream)) {
printf("读取出现异常结束\n");
}
return 1;
}
读取的字符是:a
读取的字符是:b
读取的字符是:c
读取的字符是:d
读取的字符是:e
读取的字符是:
读取到文件末尾结束
整行读写
写函数
int fputs (constchar *__restrict __s, FILE *__restrict __stream);
int puts (constchar *__s);
返回值:成功返回非负值,错误返回EOF
读函数
char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream)
char *gets (char *__s)
返回值:成功返回buf, 失败或达到文件结尾返回NULL
举例:
#include <stdio.h>
#include <string.h>
int main() {
FILE *iostream = fopen("fopenfile", "r+");
if (iostream == NULL) {
perror("文件打开错误!");
return -1;
}
int reval = fputs("hello world\n", iostream);
if (reval == -1) {
perror("文件写入失败!");
return -1;
}
fflush(iostream);
fclose(iostream);
iostream = fopen("fopenfile", "r+");
if (iostream == NULL) {
perror("文件打开错误!");
return -1;
}
char buf[1000];
memset(buf, '\0', 1000);
char *getval = fgets(buf, 1000, iostream);
printf("读入一行数据是:%s", buf);
}
二进制IO
前面说到的单字节以及整行读写,如果要写一个结构则需要每一个结构项的读写很麻烦,这就需要用到2进制IO
读操作
size_t read (void *__restrict __ptr, size_t __size,
size_t __n, FILE *__restrict __stream)
__pt:结构体(数据)指针
__size:单个结构体大小
__n:结构体数量
__stream:文件流
返回值:读或写的对象数
写操作
size_t fwrite (constvoid *__restrict __ptr, size_t __size,
size_t __n, FILE *__restrict __s);
举例:
#include <stdio.h>
#include <string.h>
typedef struct {
int no;
char name[100];
long tel;
} Info;
int main(int argc, char *argv[]) {
if (0 == strcmp(argv[1], "read")) {
FILE *ios = fopen("binaryfile", "r");
Info info;
fread(&info, sizeof(Info), 1, ios);
printf("no=%d\n", info.no);
printf("name=%s\n", info.name);
printf("tel=%ld\n", info.tel);
if (getc(ios) == -1) {
if (feof(ios)) {
printf("读取到文件末尾结束\n");
}
if (ferror(ios)) {
printf("读取出现异常结束\n");
}
}
} else if (0 == strcmp(argv[1], "write")) {
FILE *ios = fopen("binaryfile", "w");
Info info;
info.no = 1;
info.tel = 1234567;
char *name = "hello";
memcpy(info.name, name, strlen(name) + 1);
fwrite(&info, sizeof(Info), 1, ios);
}
return 1;
}
执行结果:
no=1
name=hello
tel=1234567
读取到文件末尾结束
说明:
1.生成的文件为2进制文件,如打开看到的会是乱码
2.最后需要在此尝试读入一个字符,那么流才会结束,才会使用feof等判断
文件流定位
可以设置文件位置指示器来影响一个文件读取,用法和sleek一致
获取当前文件位置指示器
long int ftell (FILE *__stream)
返回值:当前文件位置指示器
int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos);
返回值:成功返回0,失败返回非0值
当前文件位置指示器
int fseek (FILE *__stream, longint __off, int __whence);
返回值:成功返回0,失败返回非0值
int fsetpos (FILE *__stream, constfpos_t *__pos);
返回值:成功返回0,失败返回非0值
重定位文件位置指示器
void rewind (FILE *__stream);
相当于(void)fseek(stream, 0L,SEEK_SET)
返回值:成功返回0,失败返回非0值
临时文件
char *tmpnam (char *__s)
char *tempnam (constchar *__dir, constchar *__pfx)
FILE *tmpfile (void) __wur;
int mkstemp (char *__template)
#include <stdio.h>
int main() {
char name[1000];
char *reval = tmpnam(name);
printf("允许最大随机文件名个数:%d\n", TMP_MAX);
printf("文件名:%s\n", reval);
char *newname = tempnam("/home/tkf/","SDF");
printf("扩展文件名:%s\n", newname);
FILE *ios=tmpfile();
sleep(10);
fclose(ios);
printf("临时文件删除成功!\n");
return 1;
}