1.C的文件接口
"r" - 只读模式,打开文件用于读取,文件必须存在。
"w" - 写模式,打开文件用于写入,如果文件已存在则清空文件内容,如果文件不存在则创建新文件。
"a" - 追加模式,打开文件用于写入,在文件末尾添加数据,如果文件不存在则创建新文件。
"r+" - 读写模式,打开文件用于读取和写入,文件必须存在。
"w+" - 读写模式,打开文件用于读取和写入,如果文件已存在则清空文件内容,如果文件不存在则创建新文件。
"a+" - 读写模式,打开文件用于读取和写入,在文件末尾添加数据,如果文件不存在则创建新文件。
fopen/fread/fwrite/fseek/fclose
fopen函数打开文件的方式 FILE *fopen(const char *filename, const char *mode);const char *filename 参数 : 文件名 ;const char *mode 参数 : 文件的打开方式 ;
fseek;重定位流上的文件指针 int fseek(FILE *stream, long offset, int fromwhere);函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。返回值: 成功,返回0,否则返回其他值。
FILE:文件流指针 FILE文件流指针是一个typedef之后的值,本质是一个结构体
文件流指针是一个结构体,在结构体内部保存了文件描述符
文件描述符是一个正整数,其含义为fd_array数组的下标
文件流指针维护了读写缓冲区
fread/fwrite:块的概念:fread函数用于从指定的文件中读取指定尺寸的数据
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr 指向存放数据的内存块指针,该内存块的尺寸最小应该是 size * nmemb 个字节
size 指定要读取的每个元素的尺寸,最终尺寸等于 size * nmemb
nmemb 指定要读取的元素个数,最终尺寸等于 size * nmemb
stream 该参数是一个 FILE 对象的指针,指定一个待读取的文件流
返回值是实际读取到的元素个数(nmemb);如果返回值比 nmemb 参数的值小,表示可能读取到文件末尾或者有错误发生(可以使用 feof 函数或 ferror 函数进一步判断)
fwrite 函数用于将指定尺寸的数据写入到指定的文件中
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr 指向存放数据的内存块指针,该内存块的尺寸最小应该是 size * nmemb 个字节
size 指定要写入的每个元素的尺寸,最终尺寸等于 size * nmemb
nmemb 指定要写入的元素个数,最终尺寸等于 size * nmemb
stream 该参数是一个 FILE 对象的指针,指定一个待写入的文件流
返回值是实际写入到文件中的元素个数(nmemb);
如果返回值与 nmemb 参数的值不同,则有错误发生。
2.系统调用文件接口
open/read/write/lseek/close open:文件描述符 open函数的返回值如果操作成功,它将返回一个文件描述符,如果操作失败,它将返回-1。
打开文件操作使用系统调用函数open(),该函数的作用是建立一个文件描述符,其他的函数可以通过文件描述符对指定文件进行读取与写入的操作。打开文件的一般形式是:open(文件路径, 标志);
创建新文件的同时可设置文件的权限,这时函数需要增加一组实际参数,形式为:open(文件路径, 标志, 权限标志);
第三个参数是在第二个参数中有O_CREAT时才作用,如果没有,则第三个参数可以忽略
文件权限由open的mode参数和当前进程的umask掩码共同决定。
open函数是Unix下系统调用函数,操作成功返回的是文件描述符,操作失败返回的是-1,
fopen是ANSIC标准中C语言库函数,所以在不同的系统中调用不同的内核的API,返回的是一个指向文件结构的指针。
同时open函数没有缓冲,fopen函数有缓冲,open函数一般和write配合使用,fopen函数一般和fwrite配合使用。
3.文件描述符
文件描述符就是大于等于0的小整数
最小未使用原则:进程默认打开三个文件描述符0/1/2
文件描述符和文件流指针的区别:文件流指针(FILE)是一个结构体 struct _IO_FILE{...} _IO_FILE结构体当中包含文件描述符和C库提供的缓冲区
4.重定向
重定向的本质就是修改文件描述符下标对应的struct file*的内容。追加重定向和输出重定向的唯一区别就是,输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据。输入重定向就是,将我们本应该从一个文件读取数据,现在重定向为从另一个文件读取数据。
dup2:int dup2(int oldfd, int newfd);dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中,如果有必要的话我们需要先使用关闭文件描述符为newfd的文件。dup2如果调用成功,返回newfd,否则返回-1。如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。
5.动态库和静态库
动态库:
优点:节省磁盘空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码。
缺点:必须依赖动态库,否则无法运行。
表现形式
在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数做出初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。
Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便
怎么生成 g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp
怎么使用 使用gcc编译main.c生成可执行程序时,需要用-I
选项指定头文件搜索路径,用-L
选项指定库文件搜索路径,最后用-l
选项指明需要链接库文件路径下的哪一个库。
静态库:
优点:使用静态库生成可执行程序后,该可执行程序就可以独自运行,不再需要库了。
缺点:使用静态库生成可执行程序会占用大量空间,特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库,这时在内存当中就会存在大量的重复代码。
gcc/g++ 编译源代码成为目标程序(.o)ar -rc
6.简单文件系统
Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),它们主要用来记录文件的元信息和目录层次结构。
索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点是文件的唯一标识,它们之间一一对应,也同样都会被存储在硬盘中,所以索引节点同样占用磁盘空间。
目录项,也就是 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。
由于索引节点唯一标识一个文件,而目录项记录着文件的名,所以目录项和索引节点的关系是多对一,也就是说,一个文件可以有多个别字。比如,硬链接的实现就是多个目录项中的索引节点指向同一个文件。目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存子目录或文件。目录是个文件,持久化存储在磁盘,而目录项是内核一个数据结构,缓存在内存。如果查询目录频繁从磁盘读,效率会很低,所以内核会把已经读过的目录用目录项这个数据结构缓存在内存,下次再次读到相同的目录时,只需从内存读就可以,大大提高了文件系统的效率。目录项这个数据结构不只是表示目录,也是可以表示文件的。索引节点是存储在硬盘上的数据,那么为了加速文件的访问,通常会把索引节点加载到内存中。磁盘进行格式化的时候,会被分成三个存储区域,分别是超级块、索引节点区和数据块区。 - 超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等。 - 索引节点区,用来存储索引节点; - 数据块区,用来存储文件或目录数据;我们不可能把超级块和索引节点区全部加载到内存,这样内存肯定撑不住,所以只有当需要使用的时候,才将其加载进内存,它们加载进内存的时机是不同的:超级块:当文件系统挂载时进入内存;索引节点区:当文件被访问时进入内存;
如何存储文件:连续空间存放方式 非连续空间存放方式
连续空间存放方式顾名思义,文件存放在磁盘「连续的」物理空间中。这种模式下,文件的数据都是紧密相连,读写效率很高,因为一次磁盘寻道就可以读出整个文件。使用连续存放的方式有一个前提,必须先知道一个文件的大小,这样文件系统才会根据文件的大小在磁盘上找到一块连续的空间分配给文件。所以,文件头里需要指定「起始块的位置」和「长度」,有了这两个信息就可以很好的表示文件存放方式是一块连续的磁盘空间。连续空间存放的方式虽然读写效率高,但是有「磁盘空间碎片」和「文件长度不易扩展」的缺陷。
非连续空间存放方式分为「链表方式」和「索引方式」。链表的方式存放是离散的,不用连续的,于是就可以消除磁盘碎片,可大大提高磁盘空间的利用率,同时文件的长度可以动态扩展。根据实现的方式的不同,链表可分为「隐式链表」和「显式链接」两种形式。索引的实现是为每个文件创建一个「索引数据块」,里面存放的是指向文件数据块的指针列表,说白了就像书的目录一样,要找哪个章节的内容,看目录查就可以。另外,文件头需要包含指向「索引数据块」的指针,这样就可以通过文件头知道索引数据块的位置,再通过索引数据块里的索引信息找到对应的数据块。创建文件时,索引块的所有指针都设为空。当首次写入第 i 块时,先从空闲空间中取得一个块,再将其地址写到索引块的第 i 个条目。
inode:每个文件的属性集起一个唯一的编号,即inode号
7.软硬链接
软链接文件的inode号与源文件的inode号是不同的,并且软链接文件的大小比源文件的大小要小得多。软链接又叫做符号链接,软链接文件相对于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软链接文件的大小要比源文件小得多。软链接就类似于Windows操作系统当中的快捷方式。但是软链接文件只是其源文件的一个标记,当删除了源文件后,链接文件不能独立存在,虽然仍保留文件名,但却不能执行或是查看软链接的内容了。
链接文件的inode号与源文件的inode号是相同的,并且硬链接文件的大小与源文件的大小也是相同的,硬链接文件就是源文件的一个别名,一个文件有几个文件名,该文件的硬链接数就是几,与软连接不同的是,当硬链接的源文件被删除后,硬链接文件仍能正常执行,只是文件的链接数减少了一个,因为此时该文件的文件名少了一个。硬链接就是让多个不在或者同在一个目录下的文件名,同时能够修改同一个文件,其中一个修改后,所有与其有硬链接的文件都一起修改了。
软链接是一个独立的文件,有独立的inode,而硬链接没有独立的inode。
软链接相当于快捷方式,硬链接本质没有创建文件,只是建立了一个文件名和已有的inode的映射关系,并写入当前目录。