文件编程
UNIX文件系统结构
文件系统设计方法
- 文件系统通过i节点对文件进行控制和管理。其中,每个文件对应一个i节点,每个i节点具有唯一的节点号,记录了文件的属性和内容在磁盘上的存储位置。但是文件名并不记录在i节点里,而是存储在目录文件中。
- 件数据区位于i节点区之后,由众多相同容量的块组成,用于存储文件的内容。如果文件长度超过一个块的最大容量,则该文件被存储在多个块中。文件数据区中各数据块的空闲情况由超级块记录,文件系统利用超级块中的记录完成对数据块的分配与回收。
- i节点直接记录的是文件的属性,文件的具体内容存储在数据区的数据块中,i节点中仅保留一个“磁盘地址表”来记录文件内容存储的位置。
- “磁盘地址表”由13个块号组成,每个块号占用4个字节,代表了数据区中的一个数据块编号。
- 后三个块号用于做间接寻址
UNIX文件存储设计思想
UNIX文件系统的存储通过目录和文件系统共同完成。
- 目录存储了文件的层次结构,
- 文件结构存储了文件的位置和内容。
标准文件编程——C语言提供
文件控制
使用FILE结构作为文件指针,定义在stdio.h头文件中,比低级文件I/O描述符提供了I/O缓冲的功能
API有:fopen() freopen() fclose() remove() rename()
-
创建/打开——fopen()
FILE *fopen(const char *filename, const char *type);
filename:文件名,文件路径
type:文件类型 r w a(追加模式) -
文件重定向——freopen()
FILE *freopen(const char *filename, const char *type, FILE *stream);
filename:重定向到的位置
stream: 被重定向的文件
注:unix 默认打开三个文件 stdout stdin stderr -
文件关闭——fclose()
int fclose(FILE *stream);
stream:需要关闭的文件的文件指针1 -
文件删除——remove()
int remove(const char *filename); -
文件重命名——rename()
int rename(const char *oldname, const char *newname);
文件输入输出
标准文件编程分为无格式方式读写和有格式方式读写
无格式方式读写:按字符读写,按行读写和按快读写三种
有格式读写:以整形、浮点型或者字符串格式读写文件内容
无格式读写—— 字符读写
- 字符读——从文件中读出
int getc(File *stream);
int getchar(void); // 从表准输入文件中读出
int fgetc(FILE *stream);
注:字符输入函数族虽然每次读取一个字符,但是其返回值为整型,否则会读取到错误的信息。整形为读到的字符的ASCII码 - 字符写——向文件中写入
int putc(int c, FILE *stream);
int putchar(int c); // 像标准1输出文件中写入 字符c
int fputc(int c, FILE *stream); // 返回回写入的那个字符
无格式读写—— 行读写
注:行读写函数每次读取一行以换行符结束的数据,写入数据时自动输出换行符。
- 行读出
char *gets(char *s); // 从标准输入文件中读出
char *fgets(char *s,int n,FILE *stream); // 读出时包括换行符
n: 所读出的长度包括换行符不能超过 n-1 超长将进行截取
char s[1024];
fgets(s, sizeof(s), stream);
注:出于可移植性考虑,程序中数据类型长度、字符数组长度和结构体长度等数据一般不直接指定,而是采用“sizeof”形式由编译程序确定。 - 行写入
int puts(const char *s); // 向标准输出文件中写入
int fputs(const cahr *s, FILE *stream);
注:strstr(char * , char * ) 返回后面的字符串第一次出现的位置
无格式读写——块读写
#include <stdio.h>
typedef unsigned int size_t;
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);
nitems: 读写项数
例:double f[5];
fread(f, sizeof(double), 5, INSTREAM);
fwrite(f, sizeof(double), 5, OUTSTREAM);
格式化读写
文件格式化读写具有两个步骤:
- 格式自动转换
- 文件流输入输出两个过程
输入时,函数依照某种规则先将文件流中的字符串数据转化为二进制格式数据,再存入内存中。
输出时,函数依照某种规则先将内存中的二进制数据转换为字符串格式,再输出到文件流中。
注:文件格式化读写时能够自动转换的数据格式有:数据类型、精度、宽度、进制和标志等。
数据类型转换:数据的整型、浮点型、字符型、指针型等基本类型与字符串形式之间的转换过程。
数据精度转换:浮点小数在转换为字符串时需要更改精确度。数据宽度转换:指定转换字符串的长度和填充方式。
数据进制转换:二进制、八进制、十进制和十六进制之间的转换。
数据标志转换:指定转换字符串的对齐方式、正数标志和进制标志等。
格式化读写——格式化输出——写
格式化输出函数按用户指定格式将指定的数据以字符串形式输出到文件流中。
int printf(const char format, / [arg,] */…); // 格式化到标准输出文件中
int fprintf(FILE *stream, const char format, / [arg,] */…);
int sprintf(char *s, const char format, / [arg,] */…);
format : 用于指定输出格式,记录了后继的参数个数及后继参数对转换格式的要求(由普通字符串和转意字符串组成)
在C语言中转义字符串的一般格式为:
”%[标志][宽度][.精度]类型“
文件读写位置的定位
int fseek(FILE *stream, long int offset, int whence);
void rewind(FILE *stream);
long int ftell(FILE *stream); // 获得访问位置
fseek函数改变文件流stream中的访问位置,
参数whence表示文件定位方式:
SEEK_SET:从文件头开始定位,文件定位于offset处;
SEEK_CUR:从当前位置开始定位,定位于当前位置+offset处
SEEK_END:从文件尾开始定位,定位于文件末+offset处
参数offset表明了定位的位移量。
文件状态
在UNIX中,每一个流对象内部都保持了两个指示状态:错误指示状态和文件结束指示状态,函数ferror和feof分别检查这两个状态,函数strerror显示错误的提示信息
int ferror(FILE *stream);
int feof(FILE *stream);
int clearerr(FILE *stream);
#include <string.h>
char *strerror(int errnum);
低级文件编程——UNIX/LINUX操作系统提供
在unix系统中所有操作都是基于文件的操作,在一个程序运行时打卡的文件都会由唯一的文件描述符来进行标识,一个进程默认打开 stdin(0) 、 stdout(1) 、 stderr(2)
控制API
-
打开/创建文件 open()
int open(const char filename, int oflag, …/ [mode_t mode] */);
oflag : O_RDONLY O_WRONLY O_RDWR -
关闭文件 close()
int close(int fildes); -
删除文件 unlink()
int unlink(char *path); -
文件读 read()
ssize_t read(int fildes, void *buff, size_t nbytes); -
文件写 write()
ssize_t write(int fildes, const void *buff, size_t nbytes); -
文件定位 lseek()
off_t lseek(int fildes, off_t offset, int whence) -
文件重定向
int dup(int fildes); //复制文件描述符fildes到当前未使用最小可用描述符中
int dup2(int fildes, int fildes2); -
文件锁 fcntl() + struct flock *
int fcntl(int fildes, int cmd, struct flock *arg);
struct flock{
short l_type; //锁类型
short l_whence; //锁相对位置
long l_start; //开始地址偏移量
long l_len; //长度,0表示锁至文件末
short l_pid; //拥有锁的进程ID
}
目录文件编程
UNIX目录的本质就是文件,特殊的是目录文件有特殊格式:是由目录项组成的,各个目录项格式确定,在普通文件流可以任意定位,但是目录文件不行,目录文件只能定位几个特殊的位置
操作目录API
-
读取工作目录——getcwd() getwd()
char *getcwd(char *buf, size_t size); // 返回工作目录
char *getwd(char *pathname); -
更改工作目录——chdir() fchidir()
调用函数的进程必须具有对新设置目录的执行权限
int chdir(const char *path);
int fchidir(int fildes); -
目录的创建和删除 —— mkdir() rmdir()
#include </sys/stat.h>
int mkdir(const char *path, mode_t mode);
int rmdir(char *path); -
读取目录—— readdir()
DIR *opendir(const char *dirname); // 打开目录返回目录指针
struct dirent *readdir(DIR *dirp); // 将目录项读出
int closedir(DIR *dirp); // 关闭目录指针
函数readdir读取当前目录项内容存入dirp中,并移动目录文件指针到下一个目录项。
struct dirent
{
ino_t d_ino; // i 结点标号
char d_name; // 文件名称;
} -
目录定位—— seekdir()
#include <direct.h>
void seekdir(DIR *dirp, long int loc); // 从telldir位置向后偏移
void rewinddir(DIR *dirp); // 重新到流开始的位置
long int telldir(DIR *dirp); // 记录定位到位置