相信大家都下载过东西,那么大家肯定都见过那该死的进度条,每次见到它缓慢的增长都想原地爆炸,那大家有没有想过进度条是怎样实现的?通过以下的分析,会发现进度条的实现其实很简单。
而在实现进度条之前,我们先引入缓冲区的概念:
标准I/O库的缓冲主要分为3种:全缓冲、行缓冲和不缓冲
全缓冲:全缓冲就是当输入或输出时,当缓冲区被填满了之后,才会进行实际的I/O操作。
行缓冲:当在输入或输出中遇到换行符时,才进行实际I/O操作。linux下标准输出默认是行缓冲。
无缓冲:标准I/O库不进行任何字符缓冲,任何读写都是即时可见的。linux下标准错误输出默认是不缓冲。
这里就不做详细介绍了,有兴趣的可以看一下这篇博主的文章http://blog.csdn.net/imxiangzi/article/details/45917039
缓冲区的作用:
系统调用
系统调用是内核提供给上层程序的接口,能够实现内核和上层之间的交互,系统调用在内核中的实现是软中断的方式,通过相应的中断服务例程来实现,而标准库函数是在系统调用的基础之上封装的应用程序,完全运行在用户态,在必要的时候调用系统调用。编写应用程序可以直接使用系统调用也可以使用库函数,那为什么还要有库函数呢。以printf为例,在printf的实现中,在调用write之前加入了IO缓冲区,这是 一个用户空间的缓冲,首先要说明一点,系统调用是软中断,频繁调用,需要内核频繁陷入内核态,这样的效率不是很高,而printf实际是向用户空间的IO 缓冲写,在满足条件的情况下才会调用write系统调用,这样也就提高了内核的效率。
行缓冲
printf是一个行缓冲函数,先写到缓冲区,满足条件后,才将缓冲区刷到对应文件中,刷缓冲区的条件如下:
1.缓冲区填满
2.写入的字符中有‘\n’ '\r'
3.调用fflush手动刷新缓冲区
4.调用scanf要从缓冲区中读取数据时,也会将缓冲区内的数据刷新
5.当我们执行printf的进程或者程序结束的时候会主动调用flush来刷新缓冲区,所以程序结束,系统会强制刷新缓冲区
如下:
如上例子,在字符串"hello world"的后面加上'\n'和不加输出结果的现象是不同的,通过我们刚才对于缓冲区的了解,这个问题就好理解了。第一个因为在输出字符串后添加了“\n”一个行结束标志,缓冲区得到字符“\n”,就会刷新缓冲区的内容到屏幕,然后才执行sleep休眠该进程,最后结束程序。而第二个没有添加“\n”,缓冲区并没有得到“ \n”,就会先把字符串存入到缓冲区中,当该进程休眠1s后,该进程要结束了,此时缓冲区得到结束标志,就会强制刷新缓冲区的内容到屏幕上。
在了解了以上的知识后,我们回到进度条的问题上。
首先要知道进度条的特点:
1、进度条存在一个区间,不断加载数据,直到区间加载满了。
2、会显示进度,从0%~100%。
3、在下载的时候,为使用户区分当前下载是否卡住了,会出现一个旋转的小圈,表示正加载数据。
实现方法:
1、首先我们需要将‘[ ]’固定在进度条的左右两边,中间预留下空间,进度条由0%~100%的状态( 101 个状态),,在字符数组中输出一串字符时,最后以“\0”结束的,故需要一个大小为102的数组,加载成功的数据条长度由“ # ”填充。
2.我们通过一个数组,每加载成功一次,数组会多出现一个' #'标示。由于进度条是在一行内进行的,故我们需要回车,不需要换行,通过“\r”实现。再次熟悉一下' \n ' 和 ' \r '的区别:
\r : return 到当前行的最开始位置。
\n: newline 向下移动一行,但不移动左右。
Linux中\n表示回车+换行;
Windows中\r\n表示回车+换行。
3、定义一个rate标识进度条的进度,每进行一次加一,直到100时停止。
4、在进行回车前,需要进行刷新。
5、在加载进度时,为了不让其一下子输出完,通过sleep()或usleep()控制睡眠时间,sleep以秒为单位,usleep以微秒为单位,linux下sleep包含在头文件#include<unistd.h>中。
6、对于加载中旋转的小圈通过“| / - \\”逆时针进行。注意:这里出现的两次\\ ,并不是将 \ 打印了两次,而是对 \ 进行转义。
进度条程序
执行结果:
这样一个简单的进度条就实现了,由于我也是初次了解,若有什么问题,请一定要提出。