`

流和缓冲

阅读更多
    对于不带缓冲的 I/O 库而言,大多数操作都是围绕文件描述符展开的。当打开一个文件时,就返回一个文件描述符,然后该文件描述符就用于后续的 I/O 操作。而对于标准 I/O 库,其操作则是围绕流(stream)进行的。当用标准 I/O 库打开或创建一个文件时,就将一个流与该文件关联起来了。
    标准 I/O 文件流可用于单字节或多字节(“宽”)字符集,这是由流的定向(stream's orientation)来决定的。流最初被创建时是没有定向的,如若此时在该流上使用一个多字节 I/O 函数(见<wchar.h>),则将流的定向设置为宽定向的;反之则设置为字节定向的。只有两个函数可改变流的定向。fwide 函数可用于设置流的定向,freopen 函数(见打开流函数)则可清除一个流的定向。
#include<stdio.h>
#include<wchar.h>
int fwide(FILE *fp, int mode);
    /* 返回值:宽定向的流返回正值;字节定向的流返回负值;未定向的流返回 0 */

    根据 mode 参数的值,fwide 函数执行不同的工作。
    1、若 mode 为负,fwide 将试图使指定的流是字节定向的。
    2、若 mode 为正,fwide 将试图使指定的流是宽定向的。
    3、若 mode 为 0,fwide 将不试图设置流的定向,但返回标识该流定向的值。
    注意,fwide 并不改变已定向流的定向,而且也无出错返回。我们唯一可依靠的是,在调用 fwide 前先清除 errno,待 fwide 返回后再检查 errno 的值。
    当打开一个流时,标准 I/O 函数 fopen 返回一个指向 FILE 对象的文件指针。该对象通常是一个结构,它包含了标准 I/O 库为管理该流需要的所有信息,包括用于实际 I/O 的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。应用程序没必要检验 FILE 对象。为了引用一个流,需将 FILE 指针作为参数传递给每个标准 I/O 函数。
    对一个进程预定义了三个流,即标准输入、标准输出和标准错误,分别用定义在头文件<stdio.h>中的三个预定义文件指针 stdin、stdout 和 stderr 加以引用。这些流可自动地被进程使用,它们引用的文件分别与文件描述符 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 所引用的相同。
    对于任何一个给定流的系统默认缓冲类型,我们也可使用下列两个函数之一来更改(这两个函数应该在对打开流执行任何一个其他操作之前调用)。
#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
                        /* 返回值:若成功,返回 0;否则,返回非 0 */

    可使用 setbuf 函数打开或关闭缓冲机制。为了带缓冲进行 I/O,参数 buf 必须指向一个长度为 BUFSIZE 的缓冲区(该常量位于<stdio.h>中)。通常在此之后该流就是全缓冲的,但是如果该流是与一个终端设备相关,那么某些系统也可将其设置为行缓冲的。为了关闭缓冲,将 buf 设置为 NULL。
    使用 setvbuf,我们可以利用 mode 参数精确地说明所需的缓冲类型:
    1、_IOFBF:全缓冲;
    2、_IOLBF:行缓冲;
    3、_IONBF:不带缓冲。
    若指定一个不带缓冲的流,则忽略 buf 和 size 参数。如果指定全缓冲或行缓冲,则 buf 和 size 可选择地指定一个缓冲区及其长度。如果该流是带缓冲的,而 buf 是 NULL,则标准 I/O 库将根据 BUFSIZE 的值自动地为该流分配适当长度的缓冲区(某些 C 函数库实现使用 stat 结构中的成员 st_blksize 的值来决定最佳 I/O 缓冲区长度)。
    下表总结了这两个函数的动作,以及它们的各个选项。

    如果在一个函数内分配一个自动变量类的标准 I/O 缓冲区,则从该函数返回前必须先关闭该流。另外,某些实现将缓冲区的一部分用于存放它自己的管理操作信息,所以可以存放在缓冲区中的实际数据字节数少于 size。一般而言,应由系统选择缓冲区的长度,并自动分配缓冲区。在这种情况下关闭此流时,标准 I/O 库将自动释放缓冲区。
    任何时候,都可使用 fflush 函数强制冲洗一个流,它会把该流中所有未写的数据都传送至内核。如若其参数 fp 是 NULL,则此函数将导致所有输出流被冲洗。
#include <stdio.h>
int fflush(FILE *fp);    /* 返回值:若成功,返回 0;否则,返回 EOF */

    要打开一个标准 I/O 流,可使用下列 3 个函数。
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);
              /* 返回值:若成功,都返回文件指针;否则,都返回 NULL */

    这 3 个函数的区别如下:
    1、fopen 函数打开路径名为 pathname 的一个指定的文件。
    2、freopen 在一个指定的流上打开一个指定的文件,如若该流已打开,则先关闭该流。若该流已经定向,则使用 freopen 清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。
    3、fdopen 取一个已有的文件描述符,并使一个标准的 I/O 流与该描述符相结合。此函数常用于由创建管道和网络通信管道函数返回的描述符。因为这些特殊类型的文件不能用标准 I/O 函数 fopen 打开,所以必须先调用设备专用函数以获得一个文件描述符,然后用 fdopen 使一个标准 I/O 流与其相结合。
    type 参数指定对该流的读、写方式。其可选值如下表所示。

    这里使用字符 b 为 type 的一部分,可使标准 I/O 系统能够区分文本文件和二进制文件。因为 UNIX 内核并不区分这两种文件,所以在 UNIX 环境下指定字符 b 实际上并无作用。
    对于 fdopen,type 参数的意义稍有区别。因为该描述符已被打开,所以 fdopen 为写而打开并不截断该文件。
    当用追加写类型打开一个文件后,每次写都将数据写到文件的当前尾端处。如果有多个进程用标准 I/O 追加写方式打开同一文件,那么来自每个进程的数据都将被正确地写到文件中。
    当以读和写类型打开一个文件时,具有下列限制:
    * 如果中间没有 fflush、fseek、fsetpos 或 rewind,则在输出的后面不能直接跟随输入。
    * 如果中间没有 fseek、fsetpos 或 rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
    要关闭一个流,可使用 fclose 函数。
#include <stdio.h>
int fclose(FILE *fp);    /* 返回值:若成功,返回 0;否则,返回 EOF */

    在该文件被关闭之前,冲洗缓冲区中的输出数据,丢弃缓冲区中的任何输入数据。如果标准 I/O 库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
    当一个进程正常终止时,则所有带未写缓冲数据的标准 I/O 流都将被冲洗,所有打开的标准 I/O 流都将被关闭。

    另外,在 SUSv4 中还支持了内存流,虽然仍使用 FILE 指针进行访问,但其实并没有底层文件,所有的 I/O 都是通过缓冲区与主存之间来回传送字节来完成的。因为避免了缓冲区溢出,内存流非常适合于创建字符串。因为内存流只访问主存,不访问磁盘文件,所以对于把标准 I/O 流作为参数用于临时文件的函数来说,会有很大的性能提升。
    有 3 个函数可用于内存流的创建。
#include <stdio.h>
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);
                          /* 返回值:若成功,都返回流指针;否则,都返回 NULL */

    fmemopen 函数允许调用者提供缓冲区用于内存流:buf 参数指向缓冲区的开始位置,size 指定了缓冲区大小的字节数。如果 buf 为空,fmemopen 将分配 size 个字节的缓冲区。这种情况下,当流关闭时缓冲区会被释放。type 参数控制如何使用流,其可能的取值如下表所示。

    注意,这些取值与标准 I/O 流的 type 参数取值有些微小差别。
    1、无论何时以追加写方式打开内存流时,当前文件位置都设为缓冲区中的第一个 null 字节,故内存流并不适合存储二进制数据(二进制数据在数据尾端之前就可能包含有多个 null 字节)。如果缓冲区不存在 null 字节,就设为缓冲区结尾的后一个字节。当流不是以追加写方式打开时,当前位置就设为缓冲区的开始位置。
    2、如果 buf 参数是一个 null 指针,打开流进行读或者写都没有任何意义。因为此时缓冲区是通过 fmemopen 进行分配的,没有办法找到缓冲区的地址。
    3、任何时候需要增加流缓冲区中数据量以及调用 fflush、fseek、fseeko 和 fsetpos 时都会在当前位置写入一个 null 字节。
    open_memstream 函数创建的流是面向字节的,open_wmemstream 函数创建的流是面向宽字节的。它们与 fmemopen 函数的不同在于:
    1、创建的流只能写打开。
    2、不能指定自己的缓冲区,但可以分别通过 bufp 和 sizep 参数访问缓冲区地址和大小。
    3、关闭流后需要自行释放缓冲区。
    4、对流添加字节会增加缓冲区大小。
    但是在缓冲区地址和大小的使用上必须遵循一些原则。第一,缓冲区地址和长度只有在调用 fclose 或 fflush 后才有效;第二,这些值只有在下一次流写入或调用 fclose 前才有效。因为缓冲区可以增长,所以可能需要重新分配,此时缓冲区的内存地址值在下一次调用 fclose 或 fflush 时会改变。
    下列程序使用 fmemopen 函数演示了用已知模式填充缓冲区时流写入是如何操作的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BSZ	48

int main(){
	FILE *fp;
	char buf[BSZ];
	
	memset(buf, 'a', BSZ-2);
	buf[BSZ-2] = '\0';
	buf[BSZ-1] = 'X';
	printf("after memset: %s, len = %ld\n", buf, (long)strlen(buf));
	if((fp=fmemopen(buf, BSZ, "w+")) == NULL){
		printf("fmemopen failed\n");
		exit(1);
	}
	printf("initial buffer contents: %s\n", buf);
	fprintf(fp, "hello, world");
	printf("before fflush: %s\n", buf);
	fflush(fp);             // 文件指针此时位于 strlen("hello, world") 字节处
	printf("after fflush: %s\n", buf);
	printf("len of string in buf = %ld\n", (long)strlen(buf));

	memset(buf, 'b', BSZ-2);
	buf[BSZ-2] = '\0';
	buf[BSZ-1] = 'X';
	printf("after memset: %s, len = %ld\n", buf, (long)strlen(buf));
	fprintf(fp, "hello, world");
	fseek(fp, 0, SEEK_SET);    // 把文件指针重置到 buf 开头
	printf("after fseek: %s, len = %ld\n", buf, (long)strlen(buf));

	memset(buf, 'c', BSZ-2);
	buf[BSZ-2] = '\0';
	buf[BSZ-1] = 'X';
	printf("after memset: %s, len = %ld\n", buf, (long)strlen(buf));
	fprintf(fp, "hello, world");
	fclose(fp);
	printf("after fclose: %s, len = %ld\n", buf, (long)strlen(buf));

	exit(0);
}

    运行结果:
$ ./fmemopenDemo.out 
after memset: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, len = 46
initial buffer contents:          # fmemopen 在缓冲区开始处放置 null 字节
before fflush:                    # 流冲洗后缓冲区才会变化
after fflush: hello, world
len of string in buf = 12         # null 字节追加到字符串结尾
after memset: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, len = 46
after fseek: bbbbbbbbbbbbhello, world, len = 24     # fseek引起缓冲区冲洗,并追加写 null字节
after memset: cccccccccccccccccccccccccccccccccccccccccccccc, len = 46
after fclose: hello, worldcccccccccccccccccccccccccccccccccc, len = 46  # 没有追加写 null 字节
$ 

    本例子给出了冲洗内存流和追加写 null 字节的策略。写入内存流以及推进流的内容大小(相对缓冲区而言,该大小是固定的)时,null 字节会自动追加写。流内容的大小是由写入多少来确定的。
  • 大小: 22.5 KB
  • 大小: 23.2 KB
  • 大小: 12.5 KB
分享到:
评论

相关推荐

    day10【缓冲流、转换流、序列化流、打印流】-笔记1

    缓冲流分为字节缓冲流和字符缓冲流两种,分别是BufferedInputStream、BufferedOutputStream和BufferedReader、BufferedWriter。 字节缓冲流的构造方法有两个,一个是public BufferedInputStream(InputStream in),...

    Java基础知识-day10【缓冲流、转换流、序列化流、Files】.pdf

    字节缓冲流的构造方法为public BufferedInputStream(InputStream in)和public BufferedOutputStream(OutputStream out),用于创建新的缓冲输入流和缓冲输出流。缓冲流的读写方法与基本的流是一致的,我们可以通过...

    24_IO_第3天(转换流、缓冲流)_讲义

    本讲义主要探讨的是Java IO中的转换流和缓冲流,这两个概念对于高效且灵活的数据操作至关重要。 转换流(InputStreamReader和OutputStreamWriter)在Java IO系统中起到桥梁的作用,它们允许我们进行字符流与字节流...

    JavaIO实例_字节流_字符流_缓冲流_转换流IODemo

    在Java中,IO流分为两大类:字节流和字符流,每种流又有输入流和输出流之分,分别用于数据的读取和写入。 1. **字节流**: - 字节流处理的是8位的字节数据,是最基本的流类型。Java中的`InputStream`和`...

    node-stream-content:在流和缓冲区(或字符串)之间转换

    在流和缓冲区(或字符串)之间转换。 用法 var sc = require ( 'stream-content' ) ; readAll(readableStream, [encoding], callback) 从可读流中读取内容。 sc . readAll ( readableStream , encoding , ...

    流媒体系统同步机制和缓冲机制的研究与应用

    "流媒体系统同步机制和缓冲机制的研究与应用"这个主题聚焦于两个核心概念:同步和缓冲,它们是确保流媒体服务质量和用户体验的关键。 同步机制在流媒体系统中扮演着至关重要的角色。它主要解决的是在网络传输过程中...

    Java中几个文件输入流和文件输出流的研究.pdf

    Java 中的文件输入流和文件输出流研究 Java 中的文件输入流和文件输出流是 Java ...Java 中的文件输入流和文件输出流提供了对外部文件的读写操作,而缓冲输入流和缓冲输出流则通过减少读写次数来加快输入输出速度。

    使用缓冲流快速复制文件

    为了解决这个问题,Java提供了缓冲流(BufferedInputStream和BufferedOutputStream),它们能够一次性处理多个字节,显著提高了文件操作的效率。本文将详细讲解如何利用缓冲流来快速复制文件。 首先,我们需要理解...

    JavaIO实例|字节流|字符流|缓冲流|转换流IODemo

    在Java中,IO流被设计用来在不同的数据源和目的地之间传输数据,如文件、网络连接、内存缓冲区等。本教程将详细介绍Java IO中的字节流、字符流、缓冲流以及转换流,并通过IODemo实例来展示它们的用法。 ### 1. 字节...

    缓冲流的用法

    缓冲流读写的用法...

    java虚拟内存和缓冲流

    虚拟内存和缓冲流,直接粘贴到eclipse或者cmd运行即可

    彻底明白java中的IO流

    Java中的IO流是Java核心库java...总之,Java的IO流系统是强大而灵活的,提供了多种类型的流以适应不同的输入输出需求,并通过过滤流和缓冲流增强了功能。理解这些流的概念和用法,对于编写高效的Java I/O程序至关重要。

    JAVA IO流缓冲字节流缓冲字符流等流经典代码示例加注释总结.rar

    2、常用21个IO流:FileWriter、FileReader、CharArrayReader、CharArrayWriter、CharSequence、OutputStreamWriter、FileOutputStream、InputStreamReader...演示错误用法和经典用法。 4、代码的结构可查看README文件。

    《JAVA_IO流学习总结》

    三、管道流和缓冲流 - PipedInputStream和PipedOutputStream允许在不同线程间传递数据,形成管道。 - 缓冲流(BufferedInputStream/BufferedReader等)在内部维护一个缓冲区,提高读写效率,减少对底层操作系统的...

    Visual C++源代码 55 如何使用缓冲流复制文件

    Visual C++源代码 55 如何使用缓冲流复制文件Visual C++源代码 55 如何使用缓冲流复制文件Visual C++源代码 55 如何使用缓冲流复制文件Visual C++源代码 55 如何使用缓冲流复制文件Visual C++源代码 55 如何使用缓冲...

    java中的标准输入输出流

    为了提高读写效率,Java提供了多种包装流和缓冲流。 **4.1 包装流** - **InputStreamReader**: 将字节流转换为字符流。 - **OutputStreamWriter**: 将字符流转换为字节流。 **4.2 缓冲流** - **...

    关于字节输入流的缓冲区的 api

    BufferedInputStream字节输入输出流的缓冲区的使用和规则

    21.【缓冲流、转换流、序列化流、打印流】(1)_缓冲流_源码

    【缓冲流、转换流、序列化流、打印流】-笔记`文件可能包含了详细的解释和示例代码,通过阅读这份笔记,可以更深入地理解这些流的用法和优化技巧。 总之,理解并熟练运用Java的缓冲流、转换流、序列化流和打印流,...

    IO流之字符流,缓冲流.xmind

    IO流之字符流,缓冲流.xmind

    javaio流学习总结.pdf

    Java IO流是Java平台中的重要组成部分,主要用于处理输入和输出数据。...在实际开发中,根据具体需求选择合适的流类型,利用处理流和缓冲流优化性能,以及使用转换流处理不同编码格式,都是Java程序员必备的技能。

Global site tag (gtag.js) - Google Analytics