缓冲区的概念真的理解么?带你揭开缓冲区的面纱~

🏠 大家好,我是 兔7 ,一位努力学习C++的博主~💬

🍑 如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀

🚀 如有不懂,可以随时向我提问,我会全力讲解~

🔥 如果感觉博主的文章还不错的话,希望大家关注、点赞、收藏三连支持一下博主哦~!

🔥 你们的支持是我创作的动力!

🧸 我相信现在的努力的艰辛,都是为以后的美好最好的见证!

🧸 人的心态决定姿态!

🚀 本文章CSDN首发!

目录

前言

缓冲区

缓冲区是什么?

为什么要引入缓冲区呢?

缓冲区的初步认识

行缓冲和全缓冲的意义

缓冲区在哪里?

缓冲区是谁提供的?

结论:

OS的缓冲区 vs 文件缓冲


前言

        为了更好的观察,这里我都采用Linux环境进行测试!

        此博客为博主以后复习的资料,所以大家放心学习,总结的很全面,每段代码都给大家发了出来,大家如果有疑问可以尝试去调试。

        大家一定要认真看图,图里的文字都是精华,好多的细节都在图中展示、写出来了,所以大家一定要仔细哦~

        感谢大家对我的支持,感谢大家的喜欢, 兔7 祝大家在学习的路上一路顺利,生活的路上顺心顺意~!

缓冲区

缓冲区是什么?

        缓冲区 (buffer),它是内存空间的一部分。 也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

为什么要引入缓冲区呢?

        高速设备与低速设备的不匹配,势必会让高速设备花时间等待低速设备,我们可以在这两者之间设立一个缓冲区。

  1. 可以解除两者的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率。 例如:我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
  2. 可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费很多时间,因为开始读写与终止读写所需要的时间很长,如果将数据送往缓冲区,待缓冲区满后再进行传送会大大减少读写次数,这样就可以节省很多时间。例如:我们想将数据写入到磁盘中,不是立马将数据写到磁盘中,而是先输入缓冲区中,当缓冲区满了以后,再将数据写入到磁盘中,这样就可以减少磁盘的读写次数,不然磁盘很容易坏掉。

        简单来说,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来存储数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

缓冲区的初步认识

        在这一段代码中,我们可以看到是先打印完 "hello world" 之后,休眠3秒,然后再终止程序。

但是如果我们去掉 "hello world" 最后的 "\n" 呢?是先打印 "hello world" 还是先休眠呢?

        那我们先来测试一下:

         我们看到的是先休眠,然后再打印 "hello world" ,其实不然,不论怎么样,永远是先打印 "hello world" ,只不过 "hello world" 被暂时保存在了 缓冲区 里。所以,我们运行的时候,好像先运行了 sleep(3) ,最后刷新出来 "hello world" 。

缓冲:

  1. 无缓冲
  2. 行缓冲 (常见的对显示器进行刷新数据的时候)
  3. 全缓冲 (对文件(可以理解成磁盘文件)写入的时候采用全缓冲)

        内存行缓冲区的意思是:当用 printf 打印的时候,如果字符串中有 "\n" 那么就把 "\n" 及之前的内容立马刷新到显示器上。如果没有  "\n" 那么就是会将内存行缓冲区输入满了才会刷新出去。

        这也是我们上面演示的两个情况。

        全缓冲的意思是:必须将这个全缓冲区内容写满才会对应刷新到磁盘文件中,否则不会刷新。

行缓冲和全缓冲的意义

        有没有想过,为什么显示器是行缓冲,而文件是全缓冲?

        因为计算机遵循的是冯诺依曼体系,所以文件本身在系统当中都属于外设,或者属于外设之上,比如说:键盘是外设、磁盘是外设 ... ...

        换句话说,如果我们要把数据刷新到磁盘上,或者刷新到显示器上,其实都叫做往外设上面写,而大家都应该清楚,直接往外设上面写的效率是很低的,所以我们把数据积累到缓冲区里面,积累到足够多的时候再定期去刷新,这样的话效率就会提升很多。

        所以按道理来讲,其实全缓冲的效率是最高的。那么是不是显示器的行缓冲就没有意义呢?

        当然不是,可以这么理解,我们磁盘上面的文件,在写入的时候,人是不会读的,而显示器不一样,显示器是当一个人写入数据时,这个人想尽快的拿到对应的输出结果,那么就要用到行缓冲进行刷新。

        那是不是这样就用无缓冲就更好呢?当然也不是啦~因为如果是无缓冲,效率就太低了,因为无缓冲效率太低,全缓冲人看到数据又不及时。

        所以行缓冲其实是在无缓冲和全缓冲中的效率和可用性中做的一个平衡!

缓冲区在哪里?

缓冲区是谁提供的?

        在讲之前,先要几组数据进行测试:

        我们可以看到,不管是C语言提供的函数,还是Linux的函数,都是可以正常打印的。那么如果我们在返回的前面加上一个 fork() 呢?也就是创建子进程呢?

        我们可以看到,打印是正常打印的,但是我们加了 fork() 之后,在文件里写入的时候,发现内容变多了!

        结合我们上面讲的我们先来梳理一下:

        我们会发现, "hello write" 只打印了一次,而 "hello printf" 和 "hello fprintf" 打印了两次。

        所以我们就可以得出来两个结论:

  1. 重定向还是不重定向会更改进程的缓冲方式。
  2. C接口打了两次,OS API 打了一次。

        其实大家可能会怀疑,fork() 在最后啊,因为fork() 是创建子进程嘛,因为fork() 之前该打印的已经打印完了啊,直觉告诉我,fork() 是没有意义的。(因为是也是这么想的!)

        但是!虽然你是已经打完了,但是你有没有全部刷新呢?有没有全部显示呢?答案是不一定!

        这里的一份图,我再用语言串一遍:

        因为一个是往显示器上打,一个是往文件里写,所以缓冲方式会发生变化,其中往显示器上写因为都有 "\n" 所以打印完直接刷新,也就是直接就刷完了,所以就是在 fork() 之后已经就没有数据了,所以最后打印的也就是三行。

        但是当我们重定向之后,也就是从文件里写,就变成了全缓冲,变成全缓冲后我们只是从全缓冲区里写,也就是只完成了打印,但是数据没有刷新,因为不满足刷新条件。(1. 缓冲区没满 2. 程序没退出) 所以程序就继续往下跑,此时在 fork() 的时候,因为缓冲区本身是进程对应的内存区域,也就是父进程的数据,所以 fork() 的时候,父进程或子进程进行刷新,也就是进行写入了,那么这时会发生写时拷贝,也就是父进程或子进程会拷贝一份相同的数据,最后程序退出后,此时也就刷新了两次,所以最后看到的就是这种现象!


可是为什么我们用的 write 没有打印两次呢?

        先来说一下为什么会打印两次呢:

  1. 缓冲区
  2. 发生了写时拷贝

        写时拷贝是永远都会发生的,因为这时操作系统内部写好的,所以为什么会打印两次,本质就是缓冲区的存在

        而我们用的系统调用没有出现打印两次的现象,也就是说 write(系统调用) 没有缓冲区!

        因为在 fork() 之后虽然发生了写时拷贝,但是因为没有缓冲,所以数据其实早已经被刷出去了。

那么怎么理解C函数有缓冲区,而系统调用没有缓冲区呢?

结论:

         因为C库函数是在系统调用上做的一层封装,所以说明缓冲区是C语言自带的(提供的)

        所以在C语言实现这里的时候的 struct FILE 里是存在 fd(文件描述符) 和 用户缓冲区的,也就是在 struct FILE 中的。

        所以其实C语言、C++其实都是有缓冲区的,所以缓冲区里的数据就像流水一样,从程序中流进来,再定期刷出去,所以我们就把它叫做文件流。

OS的缓冲区 vs 文件缓冲

         我们发现,当我们按照左图所示的方式,关闭 1 ,然后最后 fflush(stdout) 的方式是可以写进去的,那如果我们只打印不刷新呢?

        我们会发现,三个打印并没有被刷新出来,那么为什么没有呢?为什么必须带 fflush 呢?我们之前就知道,如果打印再屏幕上,不手动刷新的话,进程结束后会自动刷新缓冲区,所以这是为什么呢?

        因为此时 printf 打印的时候文件已经重定向了,它不是从屏幕上打印了,已经是从文件里写了,所以它的缓冲方式从行缓冲变成了全缓冲,所以最后数据是存放在用户区的缓冲区中,如果不强制刷新,也就是 fflush(stdout) ,因为缓冲区没满,所以不会刷新到内核里。所以当不会刷新的时候,而且没有 fflush(stdout) 时,我们最后关的是谁?

        我们最后关的是 fd !我们不是调用的 fclose(stdout) !fd 是谁?fd 是FILE文件底层用的文件描述符,换句话说,这个数据在用户区缓冲区里,最后当进程结束的时候它也想刷新,但是发现 fd 已经关了,所以就没办法刷新了,所以最后的数据就一直在用户区的缓冲区里,最后就没有刷新到内核里,进而也就不会显示出来。

        那么如果我们用C语言的 fclose(stdout) ,C语言的 fclose(stdout) 是关 stdout , stdout 就是标准输出,标准输出底层用的是 1 文件描述符,所以 fclose(stdout) 的时候,也就是关闭文件,一定要做的操作就是将数据刷到内核里,然后进程才退出,所以最后才会有数据。

        接下来演示一下 fclose(stdout) :

        我们可以看到刷新出来了!

        我再说一遍:我们打开文件用的是系统调用,打印的时候用的是C语言,而C语言中用的 stdout 里面的文件描述符是 1 没错。但是其中我们关闭文件的时候是我们先关文件,然后程序才退出的,换句话说就是我们打印的时候,因为重定型,所以缓冲区的刷新方式变了,它是全缓冲了,所以数据会暂时存放再C语言中的用户区的缓冲区里,可是进程退出时也得刷新啊,但是当我进程退出的时候,你如果调了 close ,close 是把 stdout 所对应的文件描述符给关了,进程退出时想刷也刷不进去了,所以最后残留再用户缓冲区里的数据就没有办法刷新到底层进程指向的文件里面,所以最后是没有办法刷新的。所以我们需要关闭的是 fclose(stdout) ,就给了用户区的缓冲区刷新的机会,就可以刷到底层了。

那么如果我们既不 fclose 也不close 呢?

        我们可以看到,还是会刷新出来的。

        其实大家想一想,FILE对象是我们申请的,那么进程退出时,曾经申请的资源是要归还给操作系统的,既然归还操作系统,所以操作系统就要把你所有申请的资源要刷新的全刷新,要释放的全释放,所以最终 不 close 也不 fclose ,最终依旧是能够写到文件里的,因为进程退出的时候,操作系统一定会把数据进行刷新的。

         如上就是 缓冲区 的所有知识,如果大家喜欢看此文章并且有收获,可以支持下 兔7 ,给 兔7 三连加关注,你的关注是对我最大的鼓励,也是我的创作动力~!

        再次感谢大家观看,感谢大家支持!

  • 16
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兔7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值