一、文件IO相关知识
文件IO都不带缓冲:即每个read和write都调用内核中的相应系统调用
不带缓冲的I/O函数不是ANSI C的组成部分,但是是POSIX和XPG3的组成部分
通过文件描述符来访问文件(0~1023,其中天然打开的三个文件标准输入、标准输出、标准错误 0,1,2)
1.文件I/O常用函数:
open()/creat():creat()的功能可以通过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);
int creat(const char *pathname, mode_t mode);
open()和creat()调用成功返回文件描述符,失败返回-1,并设置errno。
open()/creat()调用返回的文件描述符一定是最小的未用描述符数字。
creat()等价于open(pathname, O_CREAT|O_WRONLY|O_TRUNC, mode)
open()可以打开设备文件,但是不能创建设备文件,设备文件必须使用mknod()创建。
1 open函数参数
pathname:被打开的件名(可包含路径名)
flags:
O_RDONLY(只读方式打开文件)、_WRONLY(可写方式打开文件)、O_RDWR(读写方式打开文件)。这三个参数互斥。
O_CREAT(如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限)
O_NOCITY(使用本参数时,如文件为终端,那么终端不可以作为调用open()系统调用的那个进程的控制终端)
O_TRUNC(如文件已经存在,那么打开文件时先删除文件中原有数据)
O_APPEND(以添加方式打开文件,所以对文件的写操作都在文件的末尾进行)
mode:被打开文件的存取权限,为8进制表示法。
2 close():关闭文件
#include <unistd.h>
int close(int fildes);
调用成功返回0,出错返回-1,并设置errno。
当一个进程终止时,该进程打开的所有文件都由内核自动关闭。
关闭一个文件的同时,也释放该进程加在该文件上的所有记录锁。
open/close示例
include <stdio.h>
int main() {
FILE *file;
file = fopen("myfile.txt", "r");
if (file == NULL) {
perror("open");
return -1;
}
fclose(file);
return 0;
}
3 read():从文件读取数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
read()调用成功返回读取的字节数,如果返回0,表示到达文件末尾,如果返回-1,表示出错,通过errno设置错误码。
读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读取的字节数。
buf参数需要有调用者来分配内存,并在使用后,由调用者释放分配的内存。
***read示例***
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd;
char buffer[100];
fd = open("myfile.txt", O_RDONLY);
if (fd == -1) {
printf("Failed to open the file.\n");
return 1;
}
ssize_t bytesRead = read(fd, buffer, sizeof(buffer)-1); //ssize_t 等同于long int
if (bytesRead == -1) {
printf("Failed to read from the file.\n");
return 1;
}
buffer[bytesRead] = '\0'; // 添加字符串的结束符
printf("Read %ld bytes: %s\n", bytesRead, buffer);
close(fd);
return 0;
}
上述代码打开了一个文件 myfile.txt,读取其中的内容,并将读取的数据存储在buffer数组中。最后,打印读取的字节数和读取的内容。
4 write():向文件写入数据
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
write()调用成功返回已写的字节数,失败返回-1,并设置errno。
write()的返回值通常与count不同,因此需要循环将全部待写的数据全部写入文件。
write()出错的常见原因:磁盘已满或者超过了一个给定进程的文件长度限制。
对于普通文件,写操作从文件的当前位移量处开始,如果在打开文件时,指定了O_APPEND参数,则每次写操作前,将文件位移量设置在文件的当前结尾处,在一次成功的写操作后,该文件的位移量增加实际写的字节数。
write示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd;
const char *data = "Hello, World!";
ssize_t bytesWritten;
fd = open("myfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
printf("Failed to open the file.\n");
return 1;
}
bytesWritten = write(fd, data, strlen(data));
if (bytesWritten == -1) {
printf("Failed to write to the file.\n");
return 1;
}
printf("Written %ld bytes: %s\n", bytesWritten, data);
close(fd);
return 0;
}
上述代码创建了一个文件 myfile.txt,并将字符串 “Hello, World!” 写入到文件中
6 lseek():获取文件偏移数
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset, int whence);
lseek参数
fd:文件描述符
offset:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移向后移)
whence(当前位置基点):
SEEK_SET:(当前位置为文件的开头,新位置为偏移量的大小)
SEEK_CUR:(当前位置为文件指针的位置,新位置为当前位置加上偏移量)
SEEK_END:(当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小)
返回值:
成功:文件的当前位移
-1:出错
注意:每个打开的文件都有一个与其相关的“当前文件位移量”,它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读/写操作都从当前文件位移量处开始,在读/写调用成功后,使位移量增加所读或者所写的字节数。
lseek()调用成功为新的文件位移量,失败返回-1,并设置errno。
lseek()只对常规文件有效,对socket、管道、FIFO等进行lseek()操作失败。
lseek()仅将当前文件的位移量记录在内核中,它并不引起任何I/O操作。
文件位移量可以大于文件的当前长度,在这种情况下,对该文件的写操作会延长文件,并形成空洞。
lseek示例
* #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int count = 0;
int fd = open(argv[1], O_RDONLY);
if(-1 == fd){
perror("open");
return -1;
}
count = lseek(fd, 0, SEEK_END);
printf("len:%d\n", count);
return 0;
}
上述代码通过lseek的偏移量实现了求文件的大小。
二 、 两种IO比较
I/O模型 文件I/O 标准I/O
缓冲方式 韭缓冲I/O 缓冲I/O
操作对象 文件描述符 流(FILE*)
打开 open() fopen () / freopen () / fdopen ()
读 read () fread()/fgetc/fgets()……
写 write () fwrite()/fputc()/fputs()...
定位 Iseek() fseek()/ftell()/rewind()/fsetpos()/fgetpos()
关闭 close () fclose ()
三、文件目录
1.文件目录获取
以下三个函数可以获取文件/目录的属性信息:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
三个函数的返回:若成功则为0,若出错则为-1,并且设置errno.
给定一个pathname的情况下:
stat函数返回一个与此命名文件有关的信息结构
fstat函数获得已在描述符filedes上打开的文件的有关信息
lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。
struct stat定义:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
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 ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B 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 status change */
};
2. 文件类型检测宏:
S_ISREG(m) is it a regular file? 普通文件
S_ISDIR(m) directory? 目录文件
S_ISCHR(m) character device? 字符设备
S_ISBLK(m) block device? 块设备
S_ISFIFO(m) FIFO (named pipe)? 管道
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) 链接文件
S_ISSOCK(m) socket? (Not in POSIX.1-1996.) 套接字文件
目录文件(directory file) 这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读许可权的任一进程都可以读该目录的内容,但只有内核可以写目录文件。
普通文件(regular file) 这是最常见的文件类型,这种文件包含了某种形式的数据。至于这种数据是文本还是二进制数据对于内核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。
字符特殊文件(character special file) 这种文件用于系统中某些类型的设备。
块特殊文件(block special file) 这种文件典型地用于磁盘设备。系统中的所有设备或者是字符特殊文件,或者是块特殊文件。
FIFO 这种文件用于进程间的通信,有时也将其称为命名管道。
套接口(socket) 这种文件用于进程间的网络通信。套接口也可用于在一台宿主机上的进程之间的非网络通信。第1 5章将用套接口进行进程间的通信。
符号连接(symbolic link) 这种文件指向另一个文件。
3.文件目录相关函数
以下两个函数用来打开一个目录,获得一个目录流:
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
两个函数的返回:若成功则返回一个目录流,若出错则为-NULL,并且设置errno.
opendir用于打开一个给定路径名的目录
fdopendir用于打开一个和文件描述符绑定的目录。
通过 文件类型检测宏,文件目录获取等实现 ls功能
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
int main(int argc, char *argv[])
{
struct stat st; //定义一个类型为struct stat 的结构体st
DIR * dp = opendir("./"); //获取当前目录
struct dirent * dir ; //定义struct dirent * 结构体
while(1)
{
dir = readdir(dp); // 读取目录到dir
if( NULL == dir ) // 如果dir为空则读到目录末尾,目录已经读完,此时退出循环
{
break;
}
lstat(dir->d_name, &st);//将文件名和斯特的地址传入lstat函数
//通过文件类型检测宏判断文件类型
//普通文件
if(S_ISREG(st.st_mode)){
printf("-");
}
//目录
if(S_ISDIR(st.st_mode)){
printf("d");
}
//字符设备
if(S_ISCHR(st.st_mode)){
printf("c");
}
//块设备
if(S_ISBLK(st.st_mode)){
printf("b");
}
//管道
if(S_ISFIFO(st.st_mode)){
printf("p");
}
//链接
if(S_ISLNK(st.st_mode)){
printf("l");
}
//套接字
if(S_ISSOCK(st.st_mode)){
printf("s");
}
//通过st结构体中的st_mode后9位判断文件权限
for(int i = 8; i >= 0; i--){
if(st.st_mode & 1 << i){
switch(i % 3){
case 0:
printf("x");
break;
case 1:
printf("w");
break;
case 2:
printf("r");
break;
}
}else{
printf("-");
}
}
struct tm *tp;
//通过st结构体获取uid和gid,再通过下面定义的两个结构体uid和gid中pw_name gr_name成员得到用户名和用户组名
struct passwd *uid;
struct group *gid;
uid = getpwuid(st.st_uid);
gid = getgrgid(st.st_gid);
printf(" %8s %8s ",uid->pw_name,gid->gr_name);
printf(" %3ld ",st.st_nlink); // st结构体的st_nlink获取文件链接数
printf(" %8ld ",st.st_size); // st结构体的st_size获取文件大小
tp = localtime(&st.st_ctime); // st结构体的st_ctime获取最后修改时间,再用localtime进行转化
printf("%02d月 %02d %02d:%02d",tp->tm_mon+1,tp->tm_mday,tp->tm_hour,tp->tm_min); // 输出时间
printf(" %s",dir->d_name); // 输出文件名
puts("");
}
return 0;
}