14 char arr[100];
15 fread(arr,sizeof(char),strlen(arr),fp);
printf("%s\n",arr);
16 fclose(fp);
17 return 0;
18 }

#### 4.fwrite写文件

1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 int main()
5 {
6 FILE\* fp=fopen("test.txt","a+");
7 if(fp==NULL)
8 {
9 printf("打开文件失败!\n");
10 exit(1);
11 }
12 char \*arr="bit education!\n";
13 fwrite(arr,sizeof(char),strlen(arr),fp);
14 fclose(fp);
15 return 0;
16 }

#### 5.fseek移动文件指针


#### 6.ftell获取文件指针当前位置

#### 7.rewind让文件指针回到文件起始位置

#### 8.fcloes关闭文件

#### 9.输出信息到显示器方法
**fwrite(msg,strlen(msg),sizeof(char),fp);//往log.txt文件里面写**

1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 FILE \*fp=fopen("log.txt","w");
6 if(NULL==fp)
7 {
8 printf("打开文件失败!\n");
9 }
10 else
11 {
12 /\* char c='A';
13 for(;c<‘Z’;c++)
14 {
15 fputc(c,fp);
16 }*/
17 const char* msg=“Hello bit!\n”;
18 // fwrite(msg,strlen(msg),sizeof(char),fp);//往log.txt文件里面写
19 fwrite(msg,strlen(msg),sizeof(char),stdout);//输出,往显示屏上输出
20
21 }
22 fclose(fp);
23 return 0;
24 }
**fwrite(msg,strlen(msg),sizeof(char),stdout);//输出,往显示屏上输出**

#### 10.stdin & stdout & stderr
* **C默认会打开三个输入输出流,分别是stdin, stdout, stderr**
* ***仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针**

## 二、系统文件I/O
#### 1.read和write的初次使用
**操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:**
**写文件**

1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<string.h>
7 int main()
8 {
9 int fd=open("myfile",O_WRONLY|O_CREAT,0644);
10 if(fd<0)
11 {
12 printf("文件打开失败!\n");
13 exit(1);
14 }
15 char\* arr="bit education!";
16 write(fd,arr,strlen(arr)-1);
17 close(fd);
18 return 0;
19 }

**读文件**

1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<fcntl.h>
5 #include<sys/stat.h>
6 #include<unistd.h>
7 int main()
8 {
9 int fd=open("myfile.txt",O_RDONLY);
10 if(fd<0)
11 {
12 printf("文件打开失败!\n");
13 exit(1);
14 }
15 char\* arr="hello bit!";
16 char buff[100];
17 read(fd,buff,strlen(arr)-1);
18 printf("%s\n",buff);
19 close(fd);
20 return 0;
21 }

#### 2.接口介绍

>
> **open man 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);
> **pathname:** 要打开或创建的目标文件
> **flags:** 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
> **参数:**
> O\_RDONLY: 只读打开
> O\_WRONLY: 只写打开
> O\_RDWR : 读,写打开
> 这三个常量,必须指定一个且只能指定一个
> O\_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
> O\_APPEND: 追加写
> **返回值:**
> 成功:新打开的文件描述符
> 失败:-1
>
>
>
**mode\_t理解:直接 man 手册,比什么都清楚。**

**open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。**


#### 3.open函数返回值
* **在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数**
* **上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。**
* **而open close read write lseek 都属于系统提供的接口,称之为系统调用接口。**
* **回忆一下我们讲操作系统概念时,画的一张图。**

**系统调用接口和库函数的关系,一目了然。所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。**
#### 4.文件描述符fd(file descriptor)
* **通过对open函数的学习,我们知道了文件描述符就是一个小整数。**

#### 5.0 & 1 & 2

* **Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。**
* **0,1,2对应的物理设备一般是:键盘,显示器,显示器。**
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 int main()
7 {
8 char buff[100];
9 int fd=read(0,buff,sizeof(buff));
10 write(1,buff,strlen(buff)-1);
11 write(2,buff,strlen(buff)-1);
12 return 0;
13 }

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针\*files, 指向一张表files\_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

#### 6.文件描述符的分配规则
**输出发现fd是3:**
1 #include<iostream>
2 #include<fcntl.h>
3 #include<sys/stat.h>
4 #include<sys/types.h>
5 #include<unistd.h>
6 int main()
7 {
8 int fd=open("myfile",O_RDONLY|O_CREAT);
9 if(fd<0)
10 {
11 std::cout<<"文件打开失败!"<<std::endl;
12 }
13 std::cout<<fd<<std::endl;
14 close(fd);
15 return 0;
16 }

**关闭0或者2,在看:**
1 #include<iostream>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<sys/stat.h>
5 #include<sys/types.h>
6 int main()
7 {
8 close(0);
9 // close(2);
10 int fd=open("myfile",O_RDONLY|O_CREAT,0664);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 return 1;
15 }
16 std::cout<<"fd:"<<fd<<std::endl;
17 close(fd);
18 return 0;
19 }

**发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files\_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。**
**----------------------最小未分配原则----------------------**

#### 7.重定向
##### 1.清空重定向
**我们发现第一次写的hello world被第二次写的你好呀覆盖了,这就是清空重定向。**

##### 2.追加重定向
**我们发现第一次写的hello没有被第二次写的你覆盖,而是追加在hello后面,这就是追加重定向。**
##### 3.重定向本质
**那如果关闭1呢?看代码:**
1 #include<iostream>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 close(1);
10 int fd=open("myfile",O_WRONLY|O_CREAT,0644);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 exit(1);
15 }
16 std::cout<<"fd:"<<fd<<std::endl;
17 close(fd);
18 return 0;
19 }

**此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <**
* **那重定向的本质是什么呢?**

#### 8.FILE

* **因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。**
* **所以C库当中的FILE结构体内部,必定封装了fd。**
**来段代码在研究一下:**
1 #include<iostream>
2 #include<fcntl.h>
3 #include<sys/stat.h>
4 #include<sys/types.h>
5 #include<string.h>
6 #include<unistd.h>
7 #include<stdio.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 return 1;
15 }
16 const char\* str1="hello printf\n";
17 const char\* str2="hello fwrite\n";
18 const char\* str3="hello write\n";
19 write(1,str3,strlen(str3));
20 printf("%s\n",str1);
21 fprintf(stdout,"%s\n",str2);
22 fork();
23 close(fd);
24 return 0;
25
26
27 }

**但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:**

* **我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!**
* **一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。**
* **printf、fwrite库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。**
* **而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。**
* **但是进程退出之后,会统一刷新,写入文件当中。**
* **但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。**
* **write 没有变化,说明没有所谓的缓冲。**
**综上: printf、fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。**
如果有兴趣,可以看看FILE结构体:
>
> **typedef struct \_IO\_FILE FILE; 在/usr/include/stdio.h**
>
>
>
>
> 在/usr/include/libio.h
> struct \_IO\_FILE {
> int \_flags; /\* High-order word is \_IO\_MAGIC; rest is flags. */
> #define \_IO\_file\_flags \_flags
> //缓冲区相关
> /* The following pointers correspond to the C++ streambuf protocol. */
> /* Note: Tk uses the \_IO\_read\_ptr and \_IO\_read\_end fields directly. */
> char* \_IO\_read\_ptr; /\* Current read pointer */
> char* \_IO\_read\_end; /\* End of get area. */
> char* \_IO\_read\_base; /\* Start of putback+get area. */
> char* \_IO\_write\_base; /\* Start of put area. */
> char* \_IO\_write\_ptr; /\* Current put pointer. */
> char* \_IO\_write\_end; /\* End of put area. */
> char* \_IO\_buf\_base; /\* Start of reserve area. */
> char* \_IO\_buf\_end; /\* End of reserve area. */
> /* The following fields are used to support backing up and undo. \*/
> char *\_IO\_save\_base; /* Pointer to start of non-current get area. \*/
> char *\_IO\_backup\_base; /* Pointer to first valid character of backup area \*/
> char *\_IO\_save\_end; /* Pointer to end of non-current get area. */
> struct \_IO\_marker *\_markers;
> struct \_IO\_FILE *\_chain;
> **int \_fileno; //封装的文件描述符**
> #if 0
> int \_blksize;
> #else
> int \_flags2;
> #endif
> \_IO\_off\_t \_old\_offset; /* This used to be \_offset but it’s too small. */
> #define \_\_HAVE\_COLUMN /* temporary */
> /* 1+column number of pbase(); 0 is unknown. */
> unsigned short \_cur\_column;
> signed char \_vtable\_offset;
> char \_shortbuf[1];
> /* char* \_save\_gptr; char* \_save\_egptr; \*/
> \_IO\_lock\_t \*\_lock;
> #ifdef \_IO\_USE\_OLD\_IO\_FILE
> };
>
>
>
#### 9.使用 dup2 系统调用

**函数原型如下:**
>
> **#include <unistd.h>
> int dup2(int oldfd, int newfd);**
>
>
>

1 #include<iostream>
2 #include<fcntl.h>
3 #include<sys/stat.h>
4 #include<sys/types.h>
5 #include<string.h>
6 #include<unistd.h>
7 #include<stdio.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 return 1;
15 }
16
17 dup2(fd,1);
//close(fd);
18 const char\* str1="hello printf\n";
19 const char\* str2="hello fwrite\n";
20 const char\* str3="hello write\n";
21 write(1,str3,strlen(str3));
22 printf("%s\n",str1);
23 fprintf(stdout,"%s\n",str2);
24 fflush(stdout);
25 close(fd);
26 return 0;
27
28
29 }

#### 10.理解文件系统
**我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据。**

* 每行包含7列:
* **模式**
* **硬链接数**
* **文件所有者**
* **组**
* **大小**
* **最后修改时间**
* **文件名**
**ls -l读取存储在磁盘上的文件信息,然后显示出来**

**其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息**

**上面的执行结果有几个信息需要解释清楚:**
**inode:**
**为了能解释清楚inode我们先简单了解一下文件系统:**


* **Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。**
* **Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子。**
* **超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。**
* **GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下。**
* **块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。**
* **inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。**
* **i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等.。**
* **数据区:存放文件内容。**

**inode编号是有限的,如果要增加inode编号,通过inode table。要找文件,先找文件inode,要找inode,先找inode ID。**

**将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作。**
>
> **[root@localhost linux]# touch abc
> [root@localhost linux]# ls -i abc
**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**


**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
csdnimg.cn/20210625172901386.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0OTE4MDkw,size_16,color_FFFFFF,t_70)
**将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作。**
>
> **[root@localhost linux]# touch abc
> [root@localhost linux]# ls -i abc
**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
[外链图片转存中...(img-4i9ACcKQ-1725759483221)]
[外链图片转存中...(img-ZSBBX0Qe-1725759483222)]
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0