学习目标:
核心思想:一切皆文件
在Linux系统编程时,认为操作的对象是文件,因此有必要知道文件的概念,文件的类型,文件的权限。实现一个用户程序我们有两种方式,(1).库函数;(2).系统调用。这两种方式分别有一套属于自己的文件操作函数标准IO,文件IO。在调用这两套函数时,会接触到一个新的概念——流指针,两套文件操作函数有一个重要的不同——缓冲区。在文件操作中,存在一类偏移函数。由偏移产生了一种特殊文件——空洞文件。空洞文件在文件复制中有着重要作用。Linux系统中,目录也可以看作是文件,因此他也有一套自己的文件操作函数目录IO。
学习内容:
【Linux中的文件】
文件的概念
文件:一组相关数据的有序集合
文件名: 这组相关数据的一个名称
文件的类型
7种
b (block) 块设备文件
c (char) 字符设备文件
d (directory) 目录文件
- (regular) 普通文件
l (link) 软连接文件
p (pipe) 管道文件
s (socket) UNIX域套接字文件
按文件内容可以分为:文本文件,二进制文件
文件的权限
drwxrwxr-x 2 linux linux 4096 Mar 22 19:31 ./
说明:
d rwx rwx r-x 2 linux linux 4096 Mar 22 19:31 ./
文件类型 相关权限 目录 所有者 所在组 文件大小 最后被修改的
【io函数的基本思路】
凡是文件,都可从这个思路出发进行思考文件操作三步骤:
1.打开
2.读写
3.关闭
【io函数需要的参数——流指针与文件描述符】
流: FILE*
含义:数据从文件当中流入和流出所体现出来的字节流叫做流
分类:二进制流: 二进制数据的流
文本流: ASCII码数据的流
FILE 结构定义的对象 FILE * 称之为流对象,也叫文件流指针。
流对象 ===》头 《===数据====》尾
FILE * fp 流指针
(1).流
FILE * fp;//流指针------关联一个文件
FILE * 实际上是指向了一块内存空间(缓存,fileno)
每个程序在启动的时候操作系统默认为其打开三个描述符与流对象匹配:
标准输入 0 ==>STDIN_FILENO === stdin
标准输出 1 ==>STDOUT_FILENO == stdout
标准报错 2 ==>STDERR_FILENO == stderr
文件描述符 很小的非负的整数 内核每打开一个文件就会获得一个文件描述符
【库函数与系统调用】
标准IO
缓存: 全缓存/行缓存
操作对象: 文件指针(流指针)FILE *
函数接口: 1.打开 --fopen
2.读写 fgetc/fputc fgets/fputs fread/fwrite
3.关闭 fclose
4.定位 fseek/ftell/rewind
优点:a.方便,功能多b.可移植性好标准
不足:c.可能存在安全性隐患
文件IO
缓存: 不带缓存
操作对象: 文件描述符 (整数)
具体操作:1.打开 --open
2.读写 --read/write
3.关闭 --close
4.定位 --lseek
优点:a.使用起来,简单功能简单b.安全性高c.设备文件----文件I0
缺点:c.很多复杂功能需要自己封装设计d.可移植性差
文件I0与标准I0的比较
标准IO实际上是由C语言的发布者对操作系统中的文件IO函数封装成的库函数,因此,我们可以理解为标准IO是C语言提供的文件操作函数,而文件IO是Linux系统提供的文件操作函数。
文件I0用于底层设备相关的开发,但是效率和安全性以及移植性没有标准I0方便。如果是纯上层开发,优先选择使用标准I0。
【不同的缓冲方式】
行缓冲,1k, terminal,主要用于人机交互stdout
缓存区满或者遇到\n刷新 1024
行缓存多是关于终端的一些操作
1.遇到\n刷新
2.缓存区满刷新
3.程序结束刷新
4.fflush刷新 fflush(stdout);
全缓冲,4k,主要用于文件的读写
缓存区满刷新缓存区 4096
对普通文件进行标准IO操作,建立
的缓存一般为全缓存
刷新条件:
1.缓存区满刷新
2.程序结束刷新
3.fflush来刷新 fflush(fp);
无缓冲,0k 主要用于出错处理信息的输出 stderr
不对数据缓存直接刷新
printf();==>>stdout
fprintf(strerr,"fopen error %s",filename);
界面交互 出错处理
缓冲区的大小是可以设置
文件IO函数
open
函数原型:int open(const char *pathname, int flags,int mode);
功能:获得一个文件描述符
参数:pathname:文件名
flags:打开权限 必选项:三者互斥,有且只能有一个
O_RDONLY
O_WRONLY
O_RDWR
可选项:
O_CREAT, 创建文件
O_EXCL, 需要和O_CREAT同时使用,表示新建的文件不存在,成功,否则open就会失败
O_TRUNC 文件内容清空
O_APPEND 文件内容追加
返回值:成功返回文件描述符 (最近最小未使用) 失败返回-1
write
函数原型:ssize_t write(int fd, const void *buf, size_t count);
功能: 通过文件描述符向文件中写一串数据
参数:fd :文件描述符
buf:要写入文件的字符串的首地址
count:要写入字符的个数
返回值:成功 实际写入的个数
失败 -1 & errno 被设置
read
函数原型:ssize_t read(int fd, void *buf, size_t count);
功能:通过文件描述符读取文件中的数据
参数:fd :文件描述符
buf : 存放数据空间的首地址
count:要读到数据的字节个数
返回值:成功 读到数据的个数
失败 -1
读到文件结尾返回0
注意:read读出来的数据,不是字符串,如果要输出字符串,需要单独进行处理。
lseek
函数原型: off_t lseek(int fd, off_t offset, int whence);
功能: 定位文件的位置
参数:fd:文件描述符
offset:偏移量正:向后偏移
负:向前偏移
零:不偏移
whence:SEEK_SET SEEK_CUR SEEK_END
返回值:成功 偏移量;失败 -1
空洞文件
在Linux系统中,创建空洞文件的方法是通过在文件中创建大量的空洞(使用lseek函数),而不实际写入数据。这样,文件系统就会将这些空洞标记为洞,而不分配实际的存储空间。
空洞文件(Sparse File)是一种特殊类型的文件,其在磁盘上并不实际占用与其大小相对应的存储空间。相反,空洞文件使用一种称为“洞”的特殊标记来表示其中的一些部分是未使用的或者空的。洞并不实际占据磁盘上的存储空间,因此文件系统会将其它部分相邻的数据紧凑地存储在磁盘上,以节省存储空间。
空洞文件通常用于以下场景:
存储大量稀疏数据:当文件中大部分内容是空的或者稀疏分布时,使用空洞文件可以节省磁盘空间。
预分配存储空间:应用程序可能需要预分配文件的空间以确保存储空间的连续性,但并不真正需要文件中的所有数据。在这种情况下,可以使用空洞文件来预分配空间,而不需要实际写入数据。
数据库管理:某些数据库系统可能使用空洞文件来优化数据存储和管理,以提高性能和节省磁盘空间。
标准IO函数
fopen
函数原型:FILE *fopen(const char *path, const char *mode);
功能:打开一个文件并建立一个流
参数:path:要打开文件的 文件名 (可以指定路径) --本质是个字符串
mode: r 只读 文件不存在报错 文件存在则只读打开
r+ 读写 文件不存在报错 文件存在则读写打开
w 只写 文件不存在则创建 文件存在则清0只写打开
w+ 写读 文件不存在则创建 文件存在则清0写读打开
a 追加可写 文件不存在则创建 文件存在则追加只写打开
a+ 追加读写 文件不存在则创建 文件存在则追加读写打开
返回值:成功返回建立的文件流指针;失败返回NULL
fputc
函数原型:int fputc(int c, FILE *stream);
功能:向流中写入一个字符
参数:c:要写入的字符
stream:文件流指针
返回值:成功返回写入的字符ASCII码值;失败返回EOF
fgetc
函数原型:int fgetc(FILE *stream);
功能:从流中读取一个字符
参数:stream:文件流指针
返回值:成功返回读到字符的ASCII码值;读到文件末尾返回EOF;失败返回EOF -1
默认的流指针:
stdin --- 标准输入
stdout --- 标准输出
stderr --- 标准出错 --- 屏幕
feof
函数原型:int feof(FILE *stream);
功能:判断当前参数stream的文件流指针是否到达文件结尾。如果到达文件结尾则返回真,否则返回假 注意:该操作一定要在一次IO操作之后判断。
参数:stream 要判断结尾的文件流对象
返回值:成功到达结尾是 真 ;否则 是假
fgets
函数原型:char *fgets(char *s, int size, FILE *stream);
功能:从stream流对象关联的文件中获取size大小字节的文本数据并存储到s对应的本地内存(栈区数组,堆区内存)
参数: s 要存储数据的本地内存
size 要获取的数据长度,单位字节。
stream 要获取的目标文件流对象,可以是stdin ,程序会阻塞等待如果是普通文件fp 则指向文件第一行数据
返回值:成功 返回指向有效数据的首地址,一般等于s的地址;失败或者文件末尾 NULL;
fgets读取结束的条件:
1.EOF 文件结束
2.\n 读到 "换行符" 则读取结束
注意:
会被保存到 buffer(保存数据的这块内存中)
3.size-1 个字符
'\0' //按字符串读写
fputs
函数原型:int fputs(const char *s, FILE *stream);
功能:从s所在的本地内存中获取一行数据,并写入stream对应的文件流对象。
参数: s 要写的信息,一般是固定的字符串或者有数据的数组。
stream 要写入的目标文件流对象
返回值:成功 nonnegative number on success ;失败 -1;
gets和fgets的区别:
1.gets是危险的,因为没有规范读 到数据的上限
2.gets会去掉从终端读入的\n字符
3.fgets会读到n个数据,如果n个数据中存在\n字符则立即停止当前的读取操作
4.fgets不会去掉从流中读到的\n字符char buf[1024];
5.fgets(buff, sizeof(buff), stdin);gets(buff);
fread
函数原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从指定的stream流对象中获取nmemeb个大小为size字节的数据块到ptr
所在的本地内存中。
参数:ptr 要存储数据的本地内存一般是数组或者结构体指针
size 单个数据块的元数据大小。最小单元的大小
nmemb 要获取的数据块的个数,拷贝的数据块个数。
stream 要获取数据的源文件流对象,如果是stdin表示从
键盘获取数据,如果是fp文件则表示从普通文件获取。
返回值:成功 小于等于nemeb的整数,表示获取的数据长度;失败 小于0,结尾 0;
fwrite
函数原型:size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE *stream);
功能:从ptr所在本地内存中取出nmemb个大小为size的数据块写入到stream流对应的文件流对象中。
参数:ptr 要写的数据块地址,一般是数组或者结构体指针
size 要写的数据块元数据大小,单位是字节
nmemb 要写的数据块的个数
stream 要写的目标文件流对象。如果是stdout则表示数据会
返回值:成功 小于等于nmemb 的个数。失败 <0
fseek
函数原型:int fseek(FILE *stream, long offset, int whence);
功能:将stream流文件中的文件指针从whence位置开始偏移offset字节的长度。
参数:stream 要移动文件指针的目标文件流对象。
注意:不支持设备文件,一般用于普通文件。
offset 要在文件内偏移的距离,单位字节。
如果值为整数,则向文件末尾偏移
如果值为负数,则向文件开头偏移
whence 偏移的起始位置,由系统定义的三个宏开始。
SEEK_SET 文件的开头位置
SEEK_CUR 文件的当前位置
SEEK_END 文件的末尾位置
返回值:成功: 返回 0 失败: -1;
如果从文件的指定位置向后偏移过程中已经超过了文件的当前末尾位置,则会自动以'\0'来填充文件内容,从而形成一种被称为"空洞文件" 的特殊文件
rewind
等效于:fseek(stream,0L,SEEK_SET);long ftell(FILE *stream);
函数原型:rewind(fp);
功能:获取当前文件流指针的具体位置,一般以文件开头到当前指针的字节数为返回值。
参数:stream 要返回指针距离的文件流对象
返回值:成功 获取到的距离长度,单位是字节;失败 -1;
fileno
函数原型:int fileno(FILE *stream);
功能:获得一个文件流指针中的文件描述符 FILE* fp -> int fd
参数:stream:文件流指针
返回值:成功返回文件描述符;失败返回-1
fdopen
函数原型:FILE *fdopen(int fd, const char *mode);
功能: 将文件描述符转化为文件流指针 int fd -> FILE* fp
参数:fd:已经打开的文件描述符
mode:"r" "r+" "w" "w+" "a" "a+";
返回值:成功返回文件流指针;失败返回NULL
目录IO函数
目录也是文件:
目录IO类似标准IO
操作对象是目录流指针
opendir
函数原型:DIR * opendir(const char *name); //cd /home/linux/tmp
功能:打开一个目录获得一个目录流指针
参数:name:目录名
返回值:成功 返回目录流指针;失败 返回NULL
readdir
函数原型:struct dirent *readdir(DIR *dirp);
功能:从目录流中读取文件信息并将保存信息的结构体地址返回
参数:dirp:目录流指针
返回值:成功 包含文件信息的结构体指针;出错或者读到目录流末尾返回 NULL
closedir
函数原型:int closedir(DIR *dirp);
功能:关闭之前已经打开的目录流对象
参数:opendir的返回结果中目录流对象
返回值: 成功 0 失败 -1;
chdir
函数原型:int chdir(const char *path);
功能:改变当前程序的工作路径
参数:path:改变到的路径
返回值:成功返回0;失败返回-1
getcwd
函数原型:char *getcwd(char *buf, size_t size);
功能:获得当前的工作路径
参数:buf:保存工作路径空间的首地址
size:保存路径空间的长度
返回值:成功返回包含路径空间的字符串首地址;失败返回NULL
mkdir
函数原型:int mkdir(const char *pathname, mode_t mode);//777 666 --x--x--x
功能:创建一个目录 666-
参数: pathname:路径
mode:mode & ~umask 0002
返回值:成功返回0;失败返回-1
rmdir
函数原型:int rmdir(const char *pathname);
功能:删除一个空目录文件
参数:pathname:目录文件的名字
返回值:成功返回0 ;失败返回-1
学习产出:
通过fgets/fputs实现一个文件的拷贝。
使用fread和fwrite方式完成任意普通文件的拷贝。
使用read,write复制文件
总结:
注:本博客仅供学习和参考;参考资料《[Linux程序设计》