I/O操作
一.分类
1. 按是否带有缓存分类,可分为:带缓存的(行缓存、全缓存)和 不带缓存的
2. 按函数类型分类,可分为:系统调用函数(open、close …) 和 标准C库函数 (fopen 、 fclose …)
二.函数概览:
1. 标准C库函数:
1) 打开和关闭
fopen freopen(流重定向) fclose
2) 文件读和写
fread fwrite fscanf fprintf fgetc fputc fgets fputs
3) 其它
setbuf setvbuf (设置缓冲类型) fflush(强制刷新缓存) feof(判断是否到文件末尾) fseek(文件读写位置移动) ftell(返回当前文件读写位置) rewind(移动文件读写位置到文件头)
2. 系统调用函数:
1) 打开创建 和 关闭
open create close
2) 读写和读写位置移动
read write lseek
3. 文件夹及文件属性操作
stat fstat |
产看文件属性 |
chmod fchmod |
更改文件访问权限 |
chown fchown lchown |
更改文件所有者 |
readdir mkdir rmdir chdir opendir |
读、建、删、更改、打开 目录 |
link symlink readsymlink |
|
ulink remove rename |
删除、删除、重命名文件 |
access |
|
truncate ftruncate |
清空文件 |
umask |
设置当前进程 创建文件掩码 |
三.名词解释:
1. 流:所有的I/O操作仅是简单的从程序移近或者移除,这种字节流就成为流。
2. 文件指针: 更准确的说,应该叫做文件结构体指针。每个被使用的文件都在内存中开辟一个区域,用来存放文件的有关信息,这些信息保存在一个结构体类型的变量中,该结构体类型由系统定义,取名为FILE。
3. 文件描述符:顺序分配的非负整数,内核用以标志一个特定进程打开的文件。一定是最小未使用的一个非负整数。 如close(0); open(“1.txt”, ”r”) 返回的应该是0。
当使用系统调用读写文件时,用open或create返回的文件描述符。
4. 文本流:在流中处理的数据是以字符出现。 在文本流中,’\n’被转化成回车符CR 和换行符LF的ASC码ODH 和 OAH。 而当输出时,反之。
如 ’2’ ’0’ ’1’ ’1’ -->> ASC: 50 48 48 49
5. 二进制流:在流中处理的是二进制序列。 若流中有字符,则用一个字节的二进制ASC码表示;若是数字,则用对应的二进制表示,对 ’\n’不做变换。
如 数字2001 -->> 0000 0111 1101 0001
6. 全缓存:当填满I/O缓存后才进行实际的I/O操作,或者满足一定条件后,系统通过malloc来获得所需要的缓冲区。当缓冲区满(长度通常为8192B),或者满足一定条件,会执行刷新操作。(也可以手动调用fflush(FILE *fp)来手动强制刷新)
可做实验 : 将一下代码补全后,运行等待几分钟使用关闭C-c 看文件是否被写入
While(1)
{
fprintf(fp,“aaaaaa”);
sleep(1);
}
7. 行缓存:当在输入或者输出中遇到新行符(’\n’)时,进行I/O操作。
可做实验: 补全以下代码,观察结果是否和想象的一样。
printf(“aaaaaaaaaaaaaaaaaaaa”);
while(1);
8. 不带缓存: 标准I/O库不对字符设备进行缓存, 例如stderr 很多人机交互界面要求不可全缓存,标准出错绝不会是全缓存。使用setbuf() setvbuf()可以更改缓存类型。
四.Tip
1. 文件打开
当一个程序被执行,系统将自动为其打开三个标准I/O“文件”。
类别 |
文件描述符 |
文件结构体指针 |
标准输入 |
STDIN_FILENO (0) |
Stdin |
标准输出 |
STDOUT_FILENO (1) |
Stdout |
标准错误输出(不带缓存) |
STDERR_FILENO (2) |
Stderr |
1) fopen FILE *fopen(const char *path, const char*mode)
模式标志位:
r |
以只读方式打开,文件必须存在,否则打开失败,返回NULL |
r+ |
以读写方式打开,文件必须存在,否则同r |
w |
以只写方式打开,若不存在则创建,若存在则清空原文件 |
w+ |
以可读可写方式打开,同w |
a |
以附加方式打开,若不存在则创建文件,若存在,写入数据会始终被添加到文件尾,不会修改到原文件内容 |
a+ |
以可读写附加方式打开,若不存在则创建,读文件默认在文件头,写文件始终在文件末尾,不会修改到原文件内容 |
当给定“b”参数时,表示以二进制方式打开文件。
当文件不存在创建时, fopen不能设定文件权限参数(open可设定)。系统会默认采用(0666 & ~umask)作为新文件的权限
2) open int open(const char *pathname, intflag, mode_t mode)
flag |
O_RDONLY |
只读方式打开 |
这三个参数互斥! 且这三个参数必须有一个! |
O_WRONLY |
只写方式打开 |
||
O_RDWR |
读写方式打开 |
||
O_CREAT |
如果文件不存在则创建新文件。 并用第三个参数为其设置权限。 |
||
O_EXCL |
当使用O_CREAT选项时文件存在,则返回出错 |
||
O_NOCTTY |
|
||
O_TRUNC |
如文件存在,那么打开文件先删除文件中原有数据 |
||
O_APPEND |
以附加方式打开文件,所有对文件的写操作都在文件的末尾进行 |
mode 为权限标志, 可以使用8进制表示 如 rw-rw-r-- à 0664, 也可以使用位或得到,如:0664 à S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |S_OROTH
open可以打开设备文件,但是不能创建设备文件,创建设备文件,必须使用mknod
2. 文件关闭
1) fclose int fclose(FILE*stream);
当关闭一个文件时,系统会自动刷新缓冲区中的数据。当系统正常结束时(直接调用exit函数或从main函数返回),则所有带有写缓存的数据的标准I/O流都会被刷新,所有打开的标准I/O流都会被关闭。
在调用fclose关闭流后对流所进行的任何操作,包括fclose其结果都是未知的。
2) close intclose (int fildes)
进程终止时,该进程打开的所有文件都由内核关闭,关闭一个文件同时,也是放该进程加在该文件上的所有记录锁。
3. 文件读
fgetc(getchar) fgets(gets) fscanf(scanf) fread
gets函数是不被推荐的,因为函数的使用者不能指定缓存的长度,这样就可能造成缓存越界。
fgets指定缓存长度为n,此函数将读到至多n-1个字符,会在末尾填 ’\0’ 在遇到’\n’或则文件结束时结束。
fread函数返回的不是读取的字节数,而是读取的对象数(向下取整)。
4. 文件写
fputc fgets fprintf fwrite
fputs函数将一个以’\0’结束的字符串输出到指定的流。终止符’\0’不会被输出。并不一定只输出一行(并没有要求只能是’\0’前是’\n’)。
puts 函数将一个以’\0’结束的字符串输出到标准输出流上。终止符’\0’不会被输出. 但是puts 最后会补上一个换行符。
fwrite 函数返回的不是写入的字节数,而是写入的对象数(向下取整)。
5. 文件读写位置控制
ftell fseek 都假定文件的位置可以放在一个长整型中。fgetpos fsetpos是ANSI C引入的,同时引入新类型fpos_t。
ftell 返回当前读写指针位置,失败返回-1L。
fseek 移动读写指针,较第三个参数一定偏移量。 SEEK_SET 文件头, SEEK_CUR 当前位置, SEEK_END 文件结束
lseek 类似于fseek。 Lseek 只对常规文件有效,对socket、pipe、fifo等都失败。
本操作不会引起任何I/O操作。 文件偏移可以大于文件的长度,并形成空洞。
rewind 移动文件读写指针到文件开始。相当 fseek(fp, 0L, SEEK_SET)
6. 临时文件
tmpnam char*tmpnam(char *s)
产生一个与现在文件名不同的一个有效路径名字符串,每次调用都会产生不同的一个路径名。若 s为NULL,则返回值放在一个静态区中,否则则认为s指向的是一个长度至少为L_tmpname个字节空间
Tmpfile FILE*tmpfile(void)
创建一个临时二进制文件(以wb+方式打开),在关闭文件或程序结束时,会自动删除这种文件。
7. 文件出错函数
feof ferror clearer
8. 文件属性操作
1) stat fstat lstat
stat intstat(const char *file_name, struct stat *buf);
函数执行状态通过返回值返回, 执行结果通过 buf返回(空间有调用者开辟)。
struct stat {
dev_t st_dev; /* device */
ino_t st_ino; /* inode */
mode_t st_mode; /* protection*/文件类型和读写执行权限
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device type (if inode device) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last change */
};
2) unask
umask mode_t umask(mode_t mask);
类似unix系统提供的umask命令的作用。 不同点是,函数umask 只修改当前进程创建文件的掩码。
3) truncate ftruncate
int truncate(const char *path, off_t length);
length可以大于文件的实际长度, 若小于,则超过length外的长度不再能访问,若大于,则扩展文件,文件产生空洞
9. 全缓冲、行缓冲、不缓冲
1) 全缓冲,只有 fflush()、缓冲区满、程序正常结束(return 或者调用exit,注:调用_exit 和 _Exit不会刷新缓存)才会进行I/O操作。通常大小为8192(8KB)或者4096(4KB).
2) 行缓存,只用遇到新行符’\n’ 、调用fflush 、缓冲区满、程序正常结束,才会进行I/O操作。大小通常为 1024(1KB)。
3) 不缓存,所有标准I/O函数都会进行I/O操作。
4) 标准错误输出 stderr always unbuffered, 一直无缓存
5) 标准输入和标准输出是全缓存。 除非关联到一个终端设备(terminal device),此时是行缓存。
6) 除以上三个以外所有流, 除非关联到一个终端设备,否则都是全缓存。
7) 可以使用 setbuf setvbuf 更改缓冲类型。 可以使用fflush 强制刷新流。
8) exit _exit _Exit
exit,调用之后,会执行一系列的清理操作,包括调用执行各终止处理程序,关闭所有标准I/O流,然后进入内核。
_exit 和 _Exit 类似,不会进行清理工作而直接进入内核。
五.应用实践
1. 实现模拟写日志
1) 每行数据类似于, 1,2013-8-13 22:30:33
2) 改程序无限循环,直到C-C中断
3) 再次启动程序时,若原日志文件存在,则接上序号。
2. 简单的ls -l 的实现。 打印出类似的信息
3. 简单的cp 的实现。
4. 简单的grep的实现。
六.Q&A
1. Q: 如何输入EOF, 如 while(scanf(“%d”,&num)); 输入什么时,程序才会结束?
A: C-e ctrl + e
2. Q: 如何在文件中插入或者删除一行?
A:通过临时文件
3. Q: 如何把stdin或者stdout 重定向到文件?
A:使用freopen
4. Q: 如何恢复3中的重定向?
A: 再次使用freopen, 如 freopen(“/dev/tty”,“w”, stdout)