标准IO自带缓冲区
为什么设置缓冲
- 从效率的角度考虑,避免频繁地呼叫系统调用(read、write);其次缓冲区大小的设置在不同OS上是有技术的,标准库为我们做了优化选择。
- 从安全角度考虑:用户态到内核态的切换。频繁的切换是不安全的。
系统自动的在内存里面为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区,在一起送到磁盘里面。从内存向磁盘读取数据,则一次从磁盘文件中将一批数据读到内存缓冲区中,然后再从缓冲区中将数据送到程序的数据区。
例如:需要将1024 个字节写到文件中去,每次写一个字节。
如果是文件IO,则要进行1024次 写操作,将1024个字节挪到内核,然后到磁盘。
如果使用标准IO的话,会现将数据写到缓冲区中,然后等到缓冲区满了,或者刷新了缓冲区,才整体将这些数据写入磁盘。只执行一次写操作即可,一次数据挪动即可。
标准IO 缓冲区分类
标准IO函数是根据文件流关联的设备类型,会选择采用何种缓冲区的操作方式。分类如下:
- 全缓冲区
这种缓冲区要求填满整个缓冲区后才进行I/O 系统调用操作。对于磁盘文件通常使用全缓冲区访问。第一次执行I/O 操作时,ANSI标准的文件管理函数通过调用malloc 函数获得需使用的缓冲区。linux默认大小为4096字节。有两种方式可以刷新缓冲区:(1)ffush() (2) 缓冲区满 - 行缓冲区
在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O系统调用操作。当流涉及一个终端时(例如标准输入和标准输出),使用行缓冲区。因为标准I/O库收集的每行的缓冲区长度是固定的,只要填满了缓冲区,即使还没有遇到换行符也将执行I/O 系统调用操作。默认行缓冲区大小为1024 字节。有三种方式可以刷新缓冲区:(1)ffush() (2) 缓冲区满(3) 遇到\n - 无缓冲区:
标准I/O 库不对字符进行缓存。如果用标准I/O 函数写若干字符到不带 缓冲区的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件。标准出错流stderr 通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。
注意:
缓冲区是通过malloc申请的空间
申请的实际,是发生I/O操作的时候
进程结束会默认刷新缓冲区。
fopen (打开文件)
-
函数原型
FILE *fopen(const char *filename, const char *mode) -
filename : 文件名称
-
mode 模式 :
模式 | 说明 |
---|---|
“r” | 打开一个用于读取的文件。该文件必须存在。 |
“w” | 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。 |
“a” | 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。 |
“r+” | 打开一个用于更新的文件,可读取也可写入。该文件必须存在。 |
“w+” | 创建一个用于读写的空文件。 |
“a+” | 打开一个用于读取和追加的文件。 |
返回值:
该函数返回一个 FILE 指针。否则返回 NULL,且设置全局变量 errno 来标识错误。
fgetc(读取一个字符)
函数原型:
int fgetc(FILE *stream); //从stream流中读取一个字符
int getc(FILE *stream); //从stream流中读取一个字符
int getchar(void); //从stdin标准输入流中流中读取一个字符
返回值
该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。
fputc(写入一个字符)
函数原型:
int fputc(int c, FILE *stream) // 将一个字符输出到stream流中
int putc(int c, FILE *stream);// 将一个字符输出到stream流中
int putchar(int c);// 将一个字符输出到stdout(标准输出)流中
返回值:
如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。
fgets (读取一行字符)
只能读取文本文件,不能读取二进制文件,因为在读取二进制文件的时候会将0认为是\0,导致0这个数据丢失。
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
函数原型:
char *fgets(char *str, int n, FILE *stream)
char *gets(char *str); 从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间
gets因为不知道顶读取数据的大小可能会导致内存超出
返回值:
如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
gets() 和 fgets的区别:
gets() 会将回车读取出来并丢弃,所以换行符不会像 scanf 那样被保留在缓冲区,也不会被 gets() 存储;而使用 fgets() 时,换行符会被 fgets() 读出来并存储在字符数组的最后,这样当这个字符数组被输出时换行符就会被输出并自动换行。
当指定的长度小于读取的长度的时候:
用 fgets() 时指定了读取的长度,如只读取 5 个字符,事实上它只能存储 4 个字符,因为最后还要留一个空间给 ‘\0’,而你却从键盘输入了多于 4 个字符,那么此时“敲”回车后换行符就不会被 fgets() 存储。数据都没有地方存放,哪有地方存放换行符呢!此时因为 fgets() 没有存储换行符,所以就不会换行了
reads in at most one less than size characters from stream and
stores them into the buffer pointed to by s. Reading stops after an
EOF or a newline. If a newline is read, it is stored into the buffer.
A terminating null byte (‘\0’) is stored after the last character in the buffer
如果发生错误,返回一个空指针。
fputs ( 写入一行字符)
函数原型:
int fputs(const char *s, FILE *stream);
int puts(const char *s); // = fputs(const char *s, stdout);
fputs() 和 puts() 有两个小区别:
puts() 只能向标准输出流输出,而 fputs() 可以向任何流输出。
使用 puts() 时,系统会在自动在其后添加换行符;而使用 fputs() 时,系统不会自动添加换行符。
返回值:
该函数返回一个非负值,如果发生错误则返回 EOF。
fwrite (写入n个对象)
可以运用与文本文件和二进制文件
函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
参数:
- ptr – 这是指向要被写入的元素数组的指针。
- size – 这是要被写入的每个元素的大小,以字节为单位。
- nmemb – 这是元素的个数,每个元素的大小为 size 字节。
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
返回值:
如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。
fread( 读取n个对象 )
可以运用与文本文件和二进制文件
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
给定流 stream 读取数据到 ptr 所指向的数组中。
返回值:
成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。
fseek、ftell和rewind
-
int fseek(FILE *stream, long int offset, int whence)
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset – 这是相对 whence 的偏移量,以字节为单位。
- whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
SEEK_SET 文件的开头 SEEK_CUR 文件指针的当前位置 SEEK_END 文件的末尾 返回值:
如果成功,则该函数返回零,否则返回非零值。
-
long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。
返回值:
该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。 -
void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。
ferror、 feof
int ferror(FILE *stream)
如果设置了与流关联的错误标识符,该函数返回一个非零值,否则返回一个零值。
int feof(FILE * stream);
当设置了与流关联的文件结束标识符时(表示读写位置已经到文件末尾),该函数返回一个非零值,否则返回零。
练习程序:
功能:
- 每隔1秒向文件中写入系统事件格式
1. 2022-10-15 15:16:42 - 该程序无限循环, 直到按下ctrl-c中断程序,
- 每次执行程序时, 系统事件追加到文件末尾, 序号递增
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//gcc -o bin/io_practice src/io_practice.c
struct tm get_cur_tm()
{
time_t timep;
if((timep = time(NULL)) < 0){
perror("time:");
exit(1);
}
struct tm *ltimep;
if((ltimep = localtime(&timep)) == NULL){
perror("localtime:");
exit(1);
}
return *ltimep;
}
int get_total_line(FILE *fp)
{
char buff[1024];
int line = 0;
while(fgets(buff, sizeof(buff), fp) != NULL){
if(buff[strlen(buff) - 1] == '\n'){
line ++;
}
}
return line;
}
int main(int argc, char *argv[])
{
if(argc < 2){
fprintf(stderr,"usage: %s <filep>\n", argv[1]);
}
FILE *fp;
if((fp = fopen(argv[1], "a+")) == NULL){
perror("fopen:");
exit(1);
}
struct tm cur_time;
int line = 1;
line = get_total_line(fp);
while(1){
printf("write date line %d\n", line);
cur_time = get_cur_tm();
fprintf(fp, "%d. %d-%02d-%02d %02d:%02d:%02d\n",
line,cur_time.tm_year + 1900, cur_time.tm_mon, cur_time.tm_mday,
cur_time.tm_hour, cur_time.tm_min, cur_time.tm_sec);
fflush(fp);
// 每格一秒钟后写入数据
sleep(1);
line ++;
}
return 0;
}
清空缓冲区
fflush(FILE *fp)
进程结束的时候,清空缓冲区