一. 文件IO学习大纲
1. 文件IO概念,文件概念,文件类型..
2. 如何在linux下系统IO访问一个文件?关闭文件?读取文件?写入文件?
3. 文件描述符概念?研究文件描述符数值。文件描述符与文件关系?
4. 文件偏移量
5. 使用文件IO -> 系统IO 进行实际应用 -> LCD液晶
6. 内存映射访问文件。
7. 文件 -> 标准IO接口,如何访问?如何关闭?如何读取?如何写入?
8. 标准IO一些特殊API函数
9. 目录IO,目录检索,访问目录,切换目录,读取目录,关闭目录..
二. linux文件概念
1. 什么是文件?
在linux下,一共有7种文件类型,连目录/硬件都属于文件。 -> 在linux下,一切都是文件。
2. 什么是文件IO?
就是对这7种文件进行数据的输出(output)/输入(input)
文件输入: -> 将数据写入到文件
文件输出: -> 将文件的内容读取出来
3. 如何实现文件的读取/写入? -> 直接使用linux系统的函数接口
手册数
2 System calls (functions provided by the kernel) -> 系统调用函数 -> 系统IO接口都是在2手册
3 Library calls (functions within program libraries) -> 库调用函数 -> 标准IO接口都是在3手册
4. 使用系统IO来访问文件与标准IO有什么区别?
系统IO -> 无缓冲区 -> 有多少数据就处理多少数据
标准IO -> 有缓冲区 -> 一块一块数据地写/读
对象: 硬件设备 -> LCD液晶、触摸屏、LED灯 -> 系统IO
对象: 普通文件 -> a.c b.txt c.jpg d.mp3 -> 标准IO
三. 使用系统IO来访问文件?
1. 如何访问文件? -> open() -> man 2 open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
pathname: 需要访问的文件的路径
flags: 操作文件的权限
O_RDONLY -> 只读
O_WRONLY -> 只写
O_RDWR -> 可读可写
返回值:
成功: 新的文件描述符 >=0 最小非负没有使用过的整数
失败: -1
2. 如何关闭文件? -> close() -> man 2 close
#include <unistd.h>
int close(int fd);
fd:需要关闭的文件描述符 -> 该数值一定是已经打开过的文件描述符。
返回值:
成功:0
失败:-1
练习1: 在ubuntu中建立一个新的文件叫test.txt,写一个程序访问该文件,并查看文件描述符的值,并关闭它!
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd,ret;
fd = open("test.txt",O_RDONLY);
printf("fd = %d\n",fd);
ret = close(fd);
printf("ret = %d\n",ret);
return 0;
}
练习2: 尝试访问板子中/dev/fb0这个文件,并关闭!
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd,ret;
fd = open("/dev/fb0",O_RDONLY);
printf("fd = %d\n",fd);
ret = close(fd);
printf("ret = %d\n",ret);
return 0;
}
四.文件描述符?
1. 什么是文件描述符呢?
文件描述符是open()函数的返回值,如果open()执行成功,那么就会返回一个非负最小并且没有使用过的整型数据。
例子:
3 = open("./test.txt");
4 = open("/1.c");
A105 = open("关国源");
其中3和4就是文件描述符,也就是说3这个数字就代表了test.txt这个文件。4代表1.c这个文件,A105代表关国源,将来只需要操作这些数字,就等价于操作对应的文件。
2. 访问文件时,发现fd等于3,说明0、1、2已经被占用,究竟是被谁占用了?
其实系统在启动时,就会默认打开3个文件,分别是"标准输入"、"标准输出"、"标准出错",它们都是一个宏定义来的,是被定义在一个头文件,头文件的路径:/usr/include/unistd.h
/* Standard file descriptors. */
#define STDIN_FILENO 0 -> 标准输入 -> 对象就是键盘 -> 理解: 0 = open("键盘")
#define STDOUT_FILENO 1 -> 标准输出 -> 对象就是屏幕 -> 理解: 1 = open("液晶屏幕") -> 有缓冲区
#define STDERR_FILENO 2 -> 标准出错 -> 对象就是屏幕 -> 理解: 2 = open("液晶屏幕错误信息") -> 无缓冲区
练习3: 验证文件描述符特性: 非负最小并且没有使用过的整型数据
当前目录下a.txt b.txt c.txt d.txt
open(a.txt);
open(b.txt);
open(c.txt);
关闭b.txt;
open("d.txt");
关闭a.txt;
open(b.txt);//3
关闭c.txt;
open(a.txt);//5
open(c.txt);//6
关闭d.txt;
? = open(d.txt); //4
分配原则:最小并且是没有使用过的int非负整型数。
3. 研究文件描述符的最大值?
例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd;
while(1)
{
fd = open("./test.txt",O_RDONLY);
printf("%d\n",fd);
if(fd == -1)
break;
}
return 0;
}
结论:文件描述符范围:0~1023
五. open()函数拓展参数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
flags: 操作文件的权限/文件的属性
必选三选一:
O_RDONLY -> 只读
O_WRONLY -> 只写
O_RDWR -> 可读可写
可选的额外参数(可选可不选):
In addition, zero or more file creation flags and file status flags can be bitwise-or'd in flags.
如果使用0或者多个标志位,那么就使用"|"连接在一起。
O_CREAT -> 如果文件不存在,则创建该文件。 -> 需要填mode这个参数,这个参数代表新文件的权限。
If the file does not exist it will be created.
mode specifies the permissions to use in case a new file is created. This argument must be supplied when O_CREAT is specified in flags; if O_CREAT is not specified, then mode is ignored.
O_EXCL -> 存在则报错
pathname already exists, then open() will fail.
O_APPEND -> 以追加的方式写入文件内容。 -> write()
O_TRUNC -> 文件存在则清空。 -> 必须三选一中只能选O_WRONLY/O_RDWR
O_TRUNC与O_CREAT一般会连用。
练习4: 自己写代码验证O_CREAT、O_TRUNC、O_EXCL这参数。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd;
fd = open("./test.txt",O_RDWR|O_CREAT|O_TRUNC,0777);
return 0;
}
六.文件的数据IO处理 -> 读取/写入数据
1. 读取文件的数据 -> read() -> man 2 read
功能: read - read from a file descriptor -> 从文件描述符中读取数据,而不是从文件中读取
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符
buf: 数据缓冲区的地址
count: 尝试从文件中读取的字节数 -> 这仅仅是一个愿望值。
返回值:
成功:实际读取到字节数
失败:-1
例题: 尝试从文件中读取一些字节出来? test.txt
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
int main(int argc,char *argv[])
{
int fd;
char buf[100] = {0};
//1. 打开文件
fd = open("./test.txt",O_RDWR); //"yueqian"
//2. 读取文件
int n;
n = read(fd,buf,5);
printf("n = %d\n",n); //5
printf("buf = %s\n",buf); //yueqi
bzero(buf,sizeof(buf));
n = read(fd,buf,5);
printf("n = %d\n",n);// 2
printf("buf = %s\n",buf); //an
//3. 关闭文件
close(fd);
return 0;
}
结论:读取文件时,文件定位会随着读取字节而偏移。
2. 写入数据到文件中 -> write() -> man 2 write
功能: write to a file descriptor -> 写入数据到文件描述符中
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd: 文件描述符
buf: 数据缓冲区
count: 写入的字节数
返回值:
成功:实际写入的字节数
失败:-1
练习5: test.txt -> 清空,将"helloworld"写入到文件中。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("test.txt",O_RDWR|O_TRUNC);
char *p = "helloworld";
write(fd,p,strlen(p));
close(fd);
return 0;
}
练习6: 验证write()文件偏移问题。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
char buf[100] = {0};
int fd = open("test.txt",O_RDWR);
read(fd,buf,2);
printf("buf = %s\n",buf);
write(fd,"abc",3);
bzero(buf,sizeof(buf));
read(fd,buf,2);
printf("buf = %s\n",buf);
close(fd);
return 0;
}
练习7: 验证open()函数的O_APPEND参数。
O_APPEND -> 如果打开文件时使用了O_APPEND,那么打开文件之后,文件的定位在文件的末尾!
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("test.txt",O_RDWR|O_APPEND);
write(fd,"apple",strlen("apple"));
close(fd);
return 0;
}
七. 文件偏移量
文件偏移量就是当前文件定位,默认打开文件时,文件定位都是在最开头。
1)IO函数可以使得偏移量进行偏移。
例子: fd = open("test.txt"); -> 偏移量为0
write(fd,"hello",5); -> 偏移量为5
2)如何使得不调用IO函数前提下偏移定位? -> lseek() -> man 2 lseek
功能: reposition read/write file offset -> 重新定位偏移量
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd: 文件描述符
offset: 偏移量的字节数
whence: 偏移的基准点
SEEK_SET -> 相对于文件开头
SEEK_CUR -> 相对于当前的位置
SEEK_END -> 相对于文件的末尾
这个几个宏是被定义在/usr/include/unistd.h
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Seek from end of file. */
返回值:
成功: 返回值距离文件开头的字节数
失败: -1
记住:
1. 新打开文件定位一定是最开头。
2. 读取文件可以导致偏移量的移动。
3. 使用lseek()可以导致偏移量的移动。
4. 相对于文件当前位置与相对于文件末尾是不一样的。
练习8: 验证lseek第三个参数。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
char buf[50] = {0};
int fd = open("test.txt",O_RDWR);
read(fd,buf,2);
printf("buf = %s\n",buf);//he
int ret = lseek(fd,5,SEEK_CUR);
printf("ret = %d\n",ret);//7
//read(fd,buf,sizeof(buf));
//printf("buf = %s\n",buf);
ret = lseek(fd,-4,SEEK_END);
printf("ret = %d\n",ret);//7
bzero(buf,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
return 0;
}