User-Buffered I/O
出于性能考虑,内核通过延迟写入、合并相邻的I/O请求和提前读取来在内部缓冲数据。通过不同的方式,用户缓冲也旨在改进性能。
Block Size
实际上,块的大小通常是512、1024、2048、4096或8192字节。
使用stat()系统调用或stat(1)命令可以很容易地计算出给定设备的块大小。
然而,通常不需要知道实际的块大小
最简单的选择是使用大缓冲区大小执行I/O,这是典型块大小的公共倍数。4096字节和8192字节都工作得很好。
但问题是程序很少用块来处理。程序处理字段、行和单个字符,而不是抽象如块。
User-Buffered I/O缩小了文件系统(以块形式)与应用程序(在其自身抽象)进行对话之间的差距。
可以在您自己的程序中手动实现用户缓冲。事实上,许多关键任务应用程序就是这样做的。然而,绝大多数程序都是standard I/O库(标准C库的一部分)或iostream库(标准C++库的一部分),它提供了一个健壮和有能力的用户缓冲解决方案。
Standard I/O
标准C库提供了标准的I/O库(通常简单地称为stdio)。
File Pointers
标准I/O例程不直接对文件描述符进行操作。相反,他们使用自己的唯一标识符,称为文件指针。在C库中,文件指针映射到文件描述符。文件指针由一个指向FILE的指针表示,该文件在<stdio.h>中定义。
Opening Files
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
Modes
mode参数描述如何打开给定文件。它是以下字符串之一:
- r
打开文件进行读取。流位于文件的开头。 - r+
打开文件进行读写。流位于文件的开头。 - w
打开文件进行写入。如果文件存在,则将其截断为零长度。如果文件不存在,则创建该文件。流位于文件的开头。 - w+
打开文件进行读写。如果文件存在,则将其截断为零长度。如果文件不存在,则创建该文件。流位于文件的开头。 - a
打开文件,以便以附加模式写入。如果文件不存在,则创建该文件。流位于文件的末尾。所有的写入都会附加到文件中。 - a+
以附加模式打开文件进行读取和写入。如果文件不存在,则会创建该文件。流位于文件末尾。所有写入都将附加到文件中。
成功后,fopen()返回一个有效的文件指针。如果失败,则返回NULL并适当地设置errno。
FILE *stream;
stream = fopen("/etc/manifest", "r");
if(!stream)
/*error*/
Opening a Stream via File Descriptor
#include <stdio.h>
FILE *fdopen(int fd, const char* mode);
mode与fopen()相同,并且必须与最初用于打开文件描述符的模式兼容。
模式w和w+是特殊的,它们不会导致截断。流定位在与文件描述符相关联的文件位置。一旦文件描述符转换为 steam,I/O不应再直接在文件描述符上执行。然而,这样做是合法的。请注意,文件描述符没有被复制,仅关联了一个新的stream。 关闭流也将关闭文件描述符。
在成功时,fdopen()返回一个有效的文件指针;如果失败,则返回NULL并适当地设置errno。
int testfdopen() {
FILE *stream;
int fd;
fd = open("../FileIO/testfile.txt", O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
stream = fdopen(fd, "w+");
if(!stream){
perror("fdopen");
return -2;
}
fclose(stream);
return 0;
}
int main()
{
testfdopen();
return 0;
}

Closing Stream
#include <stdio.h>
int fclose(FILE *stream);
首先刷新任何缓冲且尚未写入的数据。成功后,fclose()返回0。失败时,它返回EOF并相应地设置错误号。
Closing All Streams
函数关闭与当前进程关联的所有流,包括标准输入、标准输出和标准错误:
#define _GNU_SOURCE
#include <stdio.h>
int fcloseall(void);
在关闭之前,所有的流都会被冲洗。函数总是返回0;它是特定于Linux的。
Reading from a Stream
Reading a Character at a Time
#include <stdio.h>
int fgetc(FILE *stream);
int c;
c = fgetc(stream)
if(c == EOF)
/* error */
else
printf("c=%c\n", (char)c);
Putting the character back
#include <stdio.h>
int ungetc(int c, FILE *stream);
int testungetc() {
FILE *stream = fopen("test.txt", "r");
if(!stream){
perror("fopen");
return -1;
}
int ret;
ret = fgetc(stream);
if(ret == EOF){
perror("fgetc");
return -1;
}
printf("ret = %c\n", (char)ret);
ret = fgetc(stream);
if(ret == EOF){
perror("fgetc");
return -1;
}
printf("ret = %c\n", (char)ret);
ret = ungetc(ret, stream);
if(ret == EOF){
perror("ungetc");
return -1;
}
printf("ret = %c\n", (char)ret);
ret = fgetc(stream);
if(ret == EOF){
perror("fgetc");
return -1;
}
printf("ret = %c\n", (char)ret);
fclose(stream);
return 0;
}
int main()
{
testungetc();
return 0;
}

Reading an Entire Line
#include <stdio.h>
char * fgets(char *str, int size, FILE *stream);
char buf[LINE_MAX];
if(!fgets(buf, LINE_MAX, stram))
/*error*/
POSIX defines LINE_MAX in <limits.h>
int testfgets() {
FILE *stream = fopen("aaaa.txt", "r");
char buf[LINE_MAX] = {0};
if(!fgets(buf, LINE_MAX, stream)){
perror("fgets - 1");
return -1;
}
printf("1 - %s\n", buf);
if(!fgets(buf, LINE_MAX, stream)){
perror("fgets - 2");
return -1;
}
printf("2 - %s\n", buf);
fclose(stream);
return 0;
}
int main()
{
testfgets();
return 0;
}

int testfgets() {
FILE *stream = fopen("aaaa.txt", "r");
while(1) {
char buf[LINE_MAX] = {0};
if (!fgets(buf, LINE_MAX, stream)) {
perror("fgets - 1");
break;
}
printf("%s", buf);
}
return 0;
}
int main()
{
testfgets();
return 0;
}

上述测试表明:读取的最后一个字节后,将一个空字符(\0)存储在缓冲区中。因此printf打印室不用再加上\n,不然会多一个空行。
Reading arbitrary strings
char *s;
int c = 0; s = str;
while (--n > 0 && (c = fgetc (stream)) != EOF && (*s++ = c) != d) ;
if (c == d)
*--s = '\0';
else
*s = '\0';
将d设置为\n将提供类似于fget()的行为,减去在缓冲区中存储换行符。
Reading Binary Data
对于某些应用程序,读取单个字符或行是不够的。有时,开发人员希望读写复杂的二进制数据,例如C结构体。为此,standard I/O提供fread():
#include <stdio.h>
size_t fread(void *buf, size_t size, size_t nr, FILE *stream);
对fread()的调用将读取nr个数据的元素,每个大小size字节。
读取的元素数(而不是读取的字节数)会return。
该函数通过小于nr的返回值指示故障或EOF。不幸的是,如果不使用ferror()和feof(),就不可能知道这两种情况中的哪一种。
char buf[64];
size_t nr;
nr = fread (buf, 1, sizeof(buf), stream);
if (nr == 0)
/* error */
int testfread() {
FILE *stream = fopen("aaaa.txt", "r");
char buf[64] = {0};
size_t nr;
nr = fread(buf, 1, sizeof(buf), stream);
printf("nr: %d\n", nr);
printf("%s", buf);
fclose(stream);
return 0;
}
int main()
{
testfread();
return 0;
}

int testfread() {
FILE *stream = fopen("aaaa.txt", "r");
char buf[64] = {0};
size_t nr;
nr = fread(buf, 3, sizeof(buf), stream);
printf("nr: %d\n", nr);
printf("%s", buf);
fclose(stream);
return 0;
}
int main()
{
testfread();
return 0;
}

从上面两个测试可以发现 改变size的大小,返回的nr也会对应改变,这个size就是指每次读取的自己长度。
Writing to a Stream
Writing a Single Character
#include <stdio.h>
int fputc (int c, FILE *stream);
成功完成后,函数返回c。否则,返回EOF,并适当地设置errno。
if (fputc ('p', stream) == EOF)
/* error */
Writing s String of Characters
#include <stdio.h>
int fputs (const char *str, FILE *stream);
在成功时,fpust()返回一个非负数。如果失败,则返回EOF。
FILE *stream;
stream = fopen ("journal.txt", "a");
if (!stream)
/* error */
if (fputs ("The ship is made of wood.\n", stream) == EOF)
/* error */
if (fclose (stream) == EOF)
/* error */
Writing Binary Data
#include <stdio.h>
size_t fwrite (void *buf, size_t size, size_t nr, FILE *stream);
Buffered I/0 样例
int testBufferedIO() {
FILE *in, *out;
struct pirate{
char name[100];
unsigned long booty;
unsigned long beard_len;
}p, blackbeard = {"Edward Teach", 950, 48};
out = fopen("data", "w");
if(!out){
perror("fopen");
return 1;
}
if(!fwrite(&blackbeard,
1, sizeof(blackbeard), out)){
perror("fwrite");
return 1;
}
if (fclose (out)) {
perror ("fclose");
return 1; }
in = fopen ("data", "r");
if (!in) {
perror ("fopen");
return 1; }
if (!fread (&p, 1, sizeof(struct pirate), in)) {
perror ("fread");
return 1;
}
if (fclose (in)) {
perror ("fclose");
return 1;
}
printf ("name=\"%s\" booty=%lu beard_len=%u\n", p.name, p.booty, p.beard_len);
return 0;
}
int main()
{
testBufferedIO();
return 0;
}

网友评论