APUE之第5章——标准I/O库
一、知识回顾:文件I/O
-
文件 I/O 是不带缓冲的 I/O(unbuffered I/O),指每个 read 和 write 都调用内核中的一个系统调用。
-
对于内核而言,所有打开的文件都通过文件描述符引用,即不带缓冲的 I/O 通过文件描述符的方式来引用一个文件。
-
大多数文件 I/O 只需用到5个函数:open、read、write、lseek、close。
二、标准I/O
-
标准 I/O 库由 ISO C 标准说明。
-
标准 I/O 库的操作围绕流进行,即带缓冲的 I/O 则通过文件流(stream)的方式来引用文件。
-
引入标准 I/O 库的目的是为了提高 I/O 的效率,尽可能地减少使用 read 和 write 调用的次数。因为系统调用会消耗较多的资源,标准 I/O 库提供了缓冲,可以通过累积一定量的数据后,集中写入到实际的文件中来减少系统调用。这样文件的读写就要经过缓冲区做缓冲,从而提高 I/O 效率。
假设,每个人家里喝的水都是从自来水厂来的,自来水厂的水又是从水源地来的,这当中流动的水,就是水流,可以理解为IO流,
在 UNIX 中,UNIX 就是自来水厂,水源地就是源,家就是目的。则从水源到自来水厂的水流,就是IO输入流,从自来水厂到家的水流,就是IO输出流。
如果没有蓄水池,则每次用水都要运送一次。一万次用水就是运送一万次,这样的效率很低。若在水源地与自来水厂、自来水厂与家之间分别建造蓄水池,
则自来水厂可以随时从蓄水池中取水、为用户供水,我们需要用水时也能随时有水可用。这里的蓄水池就可以类比成缓冲。
三、标准 I/O 与文件 I/O 的区别
标准I/O |
文件 I/O
|
|
遵循标准
|
标准 I/O 被称为高级磁盘I/O,遵循 ANSI C 相关标准。只要开发环境中有标准 I/O库,标准 I/O 就可以使用。
|
文件 I/O 被称为低级磁盘 I/O,遵循 POSIX 相关标准。任何兼容 POSIX 标准的操作系统上都支持文件 I/O。
|
缓冲机制
|
标准 I/O 默认采用缓冲机制,可以看成是在文件 I/O 的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。
|
低级 I/O 一般没有采用缓冲,需要自己创建缓冲区。通过文件 I/O 读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销。
|
操作设备
|
标准 I/O 中用 FILE(流)表示一个打开的文件,通常用来访问普通文件。针对的是控制台,打印输出到屏幕等。
|
文件 I/O 中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。主要针对的是文件操作、读写硬盘等。
|
以下为标准 I/O 函数和文件 I/O 函数的对比
|
||
打开
|
fopen,freopen,fdopen
|
open
|
fopen与open
|
标准 I/O 使用 fopen 函数打开一个指定文件。
pathname 是文件名。type 参数指定对I/O流的读、写方式,主要有"r"(只读打开),"r+"(读写打开),"w"(只写打开),"w+"(读写打开),"a"(追加打开)、"a+"(读和追加打开),可以加上字母 b 用以指定以二进制模式打开文件。
若成功,会返回一个 FILE 类型的指针,作为我们打开文件的凭据,后面所有对这个文件的操作都需要使用这个文件指针,使用之后要调用 fclose 函数释放资源。
若失败,会返回一个指向 NULL 的指针,表示文件打开失败了,可以通过 errno 获取到具体失败的原因。
|
文件 I/O 使用 open 函数打开或创建一个文件。
path 参数是要打开或创建的文件名。aflag 参数可用来说明文件的打开模式,主要有 O_RDONLY(只读打开),O_WRONLY(只写打开),O_RDWR(读、写打开) 等。
若成功,返回文件描述符;失败则返回-1。
|
关闭
|
fclose
|
close
|
fclose与close
|
标准 I/O 使用 fclose 函数关闭一个打开的流。
如果成功关闭,返回 0,失败则返回 EOF。
|
文件 I/O 使用 close 函数关闭一个打开的文件。
如果成功关闭,返回 0,失败则返回-1,而不是 EOF。
|
读
|
getc,fgetc,getchar,
fgets,gets,
fread
|
read
|
|
标准 I/O 可以用 3 种方式进行文件读取。
函数getchar等同于getc(stdin)。前两个函数的区别是,getc可被实现为宏,而fgetc不能实现宏。
若成功,返回下一个字符;若已达到文件尾端或出错,则返回 EOF。
这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets从指定的流读。
gets(不建议使用):接受一个参数,不判断目标数组是否能够容纳读入的字符,可能导致存储溢出;
fgets使用三个参数:第一个参数和gets一样,用于存储输入的地址,第二个参数为整数,表示输入字符串的最大长度,最后一个参数就是文件指针,指向要读取的文件。
若成功,返回 buf;若已达到文件尾端或出错,则返回 NULL。
若成功,返回读或写的对象数。如果出错或达到文件尾端,则此数字可以少于 nobj。
|
文件 I/O 使用 read 函数从打开的文件中读取数据。
其中 fd 就是 open 返回的文件描述符,buf用于存储数据的目的缓冲区,而 nbytes 指定要读取的字节数。
如果成功读取,就返回读取的字节数,如已达到文件的尾端,则返回 0。如果失败,则返回-1。
|
写
|
putc,fputc,putchar,
fputs,puts,
fwrite
|
write
|
|
标准 I/O 可以用 3 种方式进行文件写入。
第一个参数是字符,第二个参数是文件指针。
putchar(c)等同于putc(stdin),putc可被实现为宏,而fputc不能。
若成功,返回c;失败则返回 EOF。
fputs 函数将一个 null 字节终止的字符串写到指定的流,尾端的终止符 null不写出。这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非null字节。通常,在 null 字节之前是一个换行符
puts 将一个 null 字节终止的字符串写到标准输出,终止符不写出。但是,puts 随后又将一个换行符写到标准输出。
若成功,返回非负值;失败则返回 EOF。
若成功,返回读或写的对象数。如果出错或达到文件尾端,则此数字少于 nobj。
|
文件 I/O 使用 write 函数向打开的文件写数据。
其中 fd 就是文件描述符,buf是要写入的内存数据,而 nbytes 指定要写的字节数。
如果成功写入,就返回已写的字节数,失败则返回-1。
|
随机存取
|
ftell,fseek
|
lseek
|
|
标准I/O 使用 fseek 和 ftell 完成文件的随机存取。
fseek 函数的第一个参数是文件指针,第二个参数是一个 long 类型的偏移量(offset),表示从起始点开始移动的距离。第三个参数就是用于指定起始点的模式,主要有 SEEK_SET(文件开始处)、SEEK_CUR(当前位置)、SEEK_END(文件结尾处)。
成功,返回0,出错,返回-1。
ftell 函数用于返回文件的当前位置,返回类型是一个 long 类型。
成功,返回当前文件位置指示,出错,返回-1L。
|
文件I/O 使用 lseek 来完成文件的随机存取。
fd 是文件描述符,offset 是偏移量,whence 指定起始点模式,取值与fseek相同:SEEK_SET,SEEK_CUR,SEEK_END,但也可以用整数0,1,2相应代替。
与fseek 不同的是,lseek有返回值,如果成功就返回指针变化前的位置,否则返回-1。
|
四、一图泛读 APUE 之标准 I/O 库
五、strace 命令跟踪 PHP 函数的系统调用
strace是一个可用于诊断、调试的Linux用户空间跟踪器,可以用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。
创建一个php文件,内容如下:
1 2 3 4 5 | <?php $file = fopen ( "test.txt" , "wr" ); fwrite( $file , "test\n" ); fclose( $file ); ?> |
执行如下命令:
1 | strace php open .php |
可以看到strace的结果:
lstat 函数返回文件的信息。
open打开文件,第二个参数是文件的打开模式,
O_WRONLY 只读打开,
O_CREAT 此文件不存在则创建,
O_TRUNC 如果此文件存在,而且为只读或读-写成功打开,则将其长度截断为0。
fstat 函数获得已在文件描述符 fd 打开文件的有关信息。
lseek 函数为一个打开的文件设置偏移量。
write 写入内容
close 关闭文件
由此可以看出,PHP文件函数其实就是文件I/O上的一些封装。
分类:
操作系统
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现