linux 之文件基础 (三)、标准IO的两个概念:流、缓冲区

1. 标准IO的一些概念

文件IO中使用了文件描述符的概念,但是并不是所有操作系统都提供文件描述符这样抽象的机制。因此 文件IO 的那一套函数,可以在类unix系统下使用。但是换做别的系统,就没有了文件描述符的概念。而c标准库,为我们提供了一套函数叫做标准IO。无论在什么系统上,我们都可以使用标准IO 所提供的函数。标准IO相当于在所有的操作系统的实现上提供了一层封装,使用标准IO 函数 进行IO操作时,在不同的操作系统上是兼容的。

标准IO提供了两方面的封装:

  • 操作对象 – FILE*: 封装了不同OS对文件的实现,
  • 封装了缓存支持和优化。

类似于我们操作文件IO时,操作的是一个文件描述符。我们操作标准IO时,是围绕流进行操作的。

2. 流的概念

在C中引入了流(stream)的概念。它将数据的输入输出看作是数据的流入和流出,这样不管是磁盘文件或者是物理设备(打印机、显示器、键盘等),都可看作一种流的源和目的,视他们为同一种东西,而不管其具体的物理结构,即对他们的操作,就是数据的流入和流出。这种把数据的输入输出操作对象,抽象化为一种流,而不管它的具体结构的方法很有利于编程,而涉及流的输出操作函数可用于各种对象,与其具体的实体无关,即具有通用性。

2.1对于输入和输出流的理解

  • 数据是流入和流出的对象,加如数据是水。
  • 存储的目标(比如一个文件)当做水池,链接水池和外界的是水管,这个水管往水池里灌水,或者把水池里的水泵出来,形成“流”。
  • 往里灌水(写文件)时,对于水管(流)来说,是“将水管里的水输出到水池里”,此时对于流来说是输出流;
  • 往外排水(读文件)时,对于水管(流)来说,是“将水池里的水输入到水管里”,此时对于流来说是输入流。

一开始总是站在水池的角度,总是想不明白,站在水管的角度就明白了。

2.2流的分类

在C中流可分为两大类,即文本流(text stream)和二进制流(binary stream)。

2.2.1 文本流

所谓文本流是指在流中流动的数据是以字符形式出现。在文本流中,’\n’被换成回车CR和换行LF的代码0DH和0AH。而当输出时,则0DH和0AH本换成’\n’。

2.2.2 二进制流

二进制流是指流动的是二进制数字序列,若流中有字符,则用一个字节的二进制ASCII码表示,若是数字,则用一个字节的二进制数表示。在流入流出时,对\n符号不进行变换。例如2001这个数,在文本流中用其ASCII码表示为:‘2’ ‘0’ ‘0’ ‘1’,共占4字节。 但是在二进制流中为其二进制形式:00000111 11010001 用十六进制就是07D1。只占两字节。

2.3 操作系统如何标识一个流

2.3.1 FILE 结构体

在操作系统内部,用一个结构体对象标识一个流,结构体对象里面记录了这个流的相关信息。 这个结构体的名字叫FILE。这个结构体有以下字段

  • 缓冲区类型
  • 当前读指针
  • 末尾指针

这样的结构体是操作系统帮我们创建的,而我们要操作这个结构体,就要知道这个结构体的位置,而FILE * 指针就是指向这个结构体的指针。当采用fopen 打开一个流时会返回一个指向流对象的指针,指针类型为FILE *

2.3.2 FILE指针

每个被使用的文件都在内存中开辟一个区域,用来存放文件的有关信息,这些信息是保存在一个结构体类型的变量中,该结构体类型是由标准IO库定义的,取名为FILE。FILE封装了系统调用中的文件描述符的概念,所以标准IO中的函数不会直接操作文件描述符。同理:FILE屏蔽了操作系统的差别(标准IO不用文件描述符也是为了屏蔽OS的概念,用FILE这个抽象的概念。而文件描述符绝对是个UNIX/LINUX的概念。)。

2.3 标准I/O预定义的3个流

一个进程启动的时候,会打开三个流:

  • 标准输入 stdin
  • 标准输出 stdout
  • 标准出错 stderr

3. 缓冲的概念

3.1 为什么设置缓冲

  • 从效率的角度考虑,避免频繁地呼叫系统调用(read、write);其次缓冲区大小的设置在不同OS上是有技术的,标准库为我们做了优化选择。
  • 从安全角度考虑:用户态到内核态的切换。频繁的切换是不安全的。

系统自动的在内存里面为每一个正在使用的文件开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存缓冲区,装满缓冲区,在一起送到磁盘里面。从内存向磁盘读取数据,则一次从磁盘文件中将一批数据读到内存缓冲区中,然后再从缓冲区中将数据送到程序的数据区。

例如:需要将1024 个字节写到文件中去,每次写一个字节。
如果是文件IO,则要进行1024次 写操作,将1024个字节挪到内核,然后到磁盘。

如果使用标准IO的话,会现将数据写到缓冲区中,然后等到缓冲区慢了,或者刷新了缓冲区,才整体将这些数据写入磁盘。只执行一次写操作即可,一次数据挪动即可。

3.2 标准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操作的时候
  • 进程结束会默认刷新缓冲区。
posted @ 2020-03-30 18:36  江南又一春  阅读(1966)  评论(0编辑  收藏  举报