一、标准IO
标准IO:标准I/O是ANSI C建立的一个标准I/O模型,是一个标 准函数包和stdio.h头中的定义,具有一定的可移植性。标准IO库处理很多细节。例如缓存分配,以优化长度执行IO等。标准的IO提供了三种类型的缓存。
(1)全缓存:有4096byte,截止条件有三,一是缓存满的时候,二是调用fflush的时候,三是进程正常结束的时候
(2)行缓存:有1024byte,它与全缓存截止的条件最大的差异就是另外遇到’\n’的时候。shell命令读取就是。
(3)不带缓存:只要用户调用该函数,就会写到内核中,stderr就是。
问:流的刷新时机?
分全缓冲、行缓冲、无缓冲三种情况讨论。
标准IO的几个经常使用函数:
1、fopen:打开一个文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
path:打开文件的路径
mode:打开文件的权限
r:只读,文件定位在开头
r+:读写,文件定位在开头
w:只写,并且会将内部内容清空,文件定位在开头,文件不存在会被创建
w+:读写,文件不存在会被创建,文件定位在开头
a:只写,文件定位在末尾,如果不存在会被创建
a+:读写,文件定位在末尾,如果不存在会被创建
返回值:
成功返回一个文件指针
失败返回NULL
fopen新建文件权限
- fopen() 创建的文件访问权限是0666(rw-rw-rw-)
- Linux系统中umask设定会影响文件的访问权限, 其规则为(0666 & ~umask)
- 用户可以通过umask函数修改相关设定
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL)
{
printf(“fopen error\n”);
return -1;
}
return 0;
}
2、fclose:关闭文件指针
#include <stdio.h>
int fclose(FILE *stream);
stream:文件操作指针
返回值:
成功返回0
失败返回EOF
fclose()调用成功返回0, 失败返回EOF, 并设置errno
流关闭时自动刷新缓冲中的数据并释放缓冲区
当一个程序正常终止时, 所有打开的流都会被关闭
流一旦关闭后就不能执行任何操作
3、处理错误信息errno,perror(),strerror()
- (1) errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了。
- (2) errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。
- (3) linux系统提供了一个函数perror(意思print error),perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后print打印出来。
extern int errno; errno 存放错误号
void perror(const char *s); perror先输出字符串s, 再输出错误号对应的错误信息
char *strerror(int errno); strerror根据错误号返回对应的错误信息
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
perror(“fopen”);
return -1;
}
/* 上面的程序与下面的程序等价 */
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
FILE *fp;
if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
printf(“fopen: %s\n”, strerror(errno));
return -1;
}
4、流的读写操作
4.1、单字符操作c
int fgetc(FILE *stream)
函数功能:从指定的流中读取一个字符。 成功返回读取的字符,读到文件结尾或者失败则返回EOF(-1)
int fputc(int c,FILE* stream)
函数功能: 向指定的流中写入一个字符。成功返回写入的字符,失败返回EOF.
4.2、字符串操作s
char * fgets(char *s,int size,FILE *stream)
函数功能:从流中最多读取size-1个字符到s保存的地址,因为必须预留位置给终止符’\0’,成功返回读入字符串的地址。失败或读到文件的尾部则返回NULL
int fputs( const char *s,FILE*stream)
函数功能: 将一个字符串写入文件,s为字符串的首地址,stream为将要写进去的文件。成功则返回写入字符的个数,失败返回-1
4.3、多字节批量操作
size_t fread(void *ptr,size_t size,size_t nmemb,FILE*stream)
功能:从一个指定的流中读取nmemb个对象,每一个对象的大小是size个字节。成功返回读取实际对象的个数(nmemb) 。失败返回0。
size_t fwrite(const void *ptr ,size_t size,size_t nmemb , FILE*stream)
功能:向一个指定的流中写nmemb个对象。函数正常返回写入实际对象的个数(nmemb) ,失败返回0。
4.4、打印和扫描操作
很折磨人的一点:fprintf与fscanf函数的FILE* 形参在首位!!!而上面其他函数的FILE*形参都在末尾!!!!!!
int fprintf(FILE *stream, const char *format, ...)
功能:打印格式化内容到文件指针当中。
stream:你想要打印的文件指针
format:数据格式,之后这段内容会打印到stream当中
…:%d,%s参数列表
int fscanf(FILE *stream, const char *format, ...)
功能:从指定的文件指针中获取格式化数据。
stream:文件指针
format:指定格式
…:%d,%s参数列表
5、流的刷新(将数据从缓冲区打入文件)
int fflush(FILE *stream)
6、定位流(offset)
6.1、ftell:返回offset值,即当前位置
long ftell(FILE *stream);
6.2、rewind:将操作位置直接定位到文件开头
void rewind(FILE *stream);
6.3、fseek:移动操作位置
int fseek(FILE *stream, long offset, int whence);
stream:文件指针
offset:偏移多少个字节,偏移量
whence:从哪里开始偏移,基地址
SEEK_SET(文件头), SEEK_CUR(当前位置), or SEEK_END(文件尾)
返回值:
成功返回0
失败返回-1
7、判断流
int ferror(FILE *stream);
判断文件操作是否出错, 如果出错返回真(非0值)
void clearerr(FILE *stream);
清除文件操作中的错误标志,以不至于每次调用ferror都判断出错
int feof(FILE *stream);
判断文件操作是否到达文件末尾,如果到达则返回真
8、如何实现文件拷贝?
#include <stdio.h>
int main(int argc, char *argv[]){
int n;
char buf[30];
if (argc < 3){
printf("Usage:%s <src_file> <file_file>\n",argv[0]);
return -1;
}
FILE *fp1, *fp2;
if ((fp1 = fopen(argv[1], "rb")) == NULL){
perror("fopen src_file");
return -1;
}
if ((fp2 = fopen(argv[2], "wb")) == NULL){
perror("fopen dest_file");
return -1;
}
/* fread返回读取的数据数 */
while((n = fread(buf, sizeof(char), 30, fp1)) > 0){
fwrite(buf, sizeof(char), n, fp2);
}
fclose(fp1);
fclose(fp2);
/* 打印调试信息 */
printf("%s,%s,%d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
9、如何实现获取文件大小??
#include <stdio.h>
int main(int argc, char *argv[]){
FILE *fp;
if (argc < 2){
printf("Usage: %s %s\n",argv[0],argv[1]);
}
if((fp = fopen(argv[1], "r")) == NULL){
perror("fopen");
return -1;
}
fseek(fp, 0, SEEK_END);
printf("%ld\n",ftell(fp));
fclose(fp);
return 0;
}
二、文件IO
文件IO:文件IO称之为不带缓存的IO(unbuffered I/O)。它们都属于系统调用,只不过在用户层没有缓存,所以叫做无缓存IO,但对于内核来说,还是进行了缓存,只是用户层看不到罢了。
1、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(操作标志位):
O_RDONLY, O_WRONLY, or O_RDWR
以上三个必须从中选择一个写入flags参数中
以下参数需要掌握一下:
O_APPEND:追加,在文件后面开始输入
O_CREAT:创建文件,当文件不存在的情况下创建这个文件,需要在后面添加创建文件的权限(mode)
创建文件之后的权限是mode&~umask
O_EXCL:跟上面的创建配套使用,如果文件存在了则打开失败
O_DIRECTORY:判断文件是否是目录,是目录则打开失败
O_NONBLOCK or O_NDELAY:不阻塞的打开文件,读写操作的时候不会卡住
O_TRUNC:打开文件的时候清空里面的内容
返回值:
返回一个文件描述符(就是文件的代号)
注意:
默认情况下,应用已经打开了三个文件0(标准输入),1(标准输出),2(标准出错)
2、close
#include <unistd.h>
int close(int fd);
fd:关闭哪个文件描述符
返回值:
成功返回0,失败返回-1
3、read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd:读取哪个文件
buf:读取的内容存放到哪里去
count:读取多少字节的数据
返回值:
成功返回读取到多少个字节
失败返回-1
4、write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:写入哪个文件
buf:写入的内容是什么
count:写入多少个字节
返回值:
成功返回写入多少个字节
失败返回-1
5、lseek
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd:重置哪个文件
offset:偏移多少个单位(字节)
whence:从哪里开始偏移
SEEK_SET:文件开头
SEEK_CUR:文件当前操作位置
SEEK_END:文件末尾
返回值:
成功返回定位的位置距离头部有多少个字节
失败返回-1
6、访问目录文件
opendir:打开一个目录文件(不是进入目录)
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
name:打开的目录具体位置
返回值:返回一个目录操作指针
失败返回NULL
closedir:关闭一个目录文件
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
dirp:目录指针
返回值:成功返回0,失败-1
readdir:获得目录中的一个成员结构体
一次调用只会读目录中一个文件的信息,如果你想要获取一个目录中的所有文件,
那你就要一直读
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
dirp:目录指针
返回值:
成功则获取到一个目录中一个成员的结构体,
失败或者是没有文件可以读了,则返回NULL
struct dirent {
ino_t d_ino; /* inode number */ //节点数(索引号)
off_t d_off; /* offset to the next dirent *///文件偏移量
unsigned short d_reclen; /* length of this record */ //记录长度,比你的文件名要大
unsigned char d_type; /* type of file; not supported by all file system types */ //文件类型
char d_name[256]; /* filename */ //文件名
};
判断d_type是否为以下值便可以判断对应的文件类型
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type is unknown.
7、修改文件权限chomd
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
成功返回0,失败返回EOF与设置errno
8、获取文件属性stat
stat无法识别链接文件,lstat则可以。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
path:你要查看的文件的位置
buf:存放文件属性的结构体,结构体解释如下:
返回值:成功返回0,错误返回-1
struct stat {
dev_t st_dev; /* ID of device containing file */常规文件的ID
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 */ 用户ID
gid_t st_gid; /* group ID of owner */ 组ID
dev_t st_rdev; /* device ID (if special file) */特殊设备ID
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 */有几块数据,每一块的单位是512字节
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 */ 文件属性更改时间
};
如果你是一个常规文件,你想要获取设备节点号major和minor来获取
如果你想要获取文件类型:把st_mode传给以下函数就能判断文件类型,如果是则返回真,不是返回假
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.) 套接字文件
9、实现文件拷贝函数
注意点:
1、创建新文件需要给定权限mode
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int n, fd_src, fd_dest;
char buf[30];
if (argc < 3){
printf("Usage:%s <src_file> <file_file>\n",argv[0]);
return -1;
}
if ((fd_src = open(argv[1], O_RDONLY)) == -1){
perror("open src_file");
return -1;
}
if ((fd_dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1){
perror("open dest_file");
return -1;
}
while((n = read(fd_src, buf, 30)) > 0){
write(fd_dest, buf, n);
}
close(fd_src);
close(fd_dest);
printf("%s,%s,%d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
10、实现打印文件目录函数
注意点:
1、目录文件中,每一个item都是一个结构体,必须得逐个读取。
2、需要2个指针,一个目录文件指针,一个是目录项指针。
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
int main(int argc, char *argv[]){
if (argc < 2){
printf("Usag: %s <dir_file>\n", argv[0]);
}
DIR *dirp;
struct dirent *dp;
if ((dirp = opendir(argv[1])) == NULL){
perror("opendir");
return -1;
}
while((dp = readdir(dirp)) != NULL){
printf("%s\n", dp->d_name);
}
closedir(dirp);
}
/*
struct dirent {
ino_t d_ino; * inode number * //节点数(索引号)
off_t d_off; * offset to the next dirent * //文件偏移量
unsigned short d_reclen; * length of this record * //记录长度,比你的文件名要大
unsigned char d_type; * type of file; not supported by all file system types *
char d_name[256]; * filename * //文件名
};
判断d_type是否为以下值便可以判断对应的文件类型
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type is unknown.
*/
11、如何获取文件大小?
size = lseek(fd, 0, SEEK_END);
等价于
fseek(fp, 0, SEEK_END);
size = ftell(fp);
三、两者的区别
-
文件I/O主要针对文件操作,读写硬盘等,它操作的是文件描述符,标准I/O针对的是控制台,打印输出到屏幕等,它操作的是字符流。对于不同设备得特性不一样,必须有不同api访问才最高效。
-
无缓存IO操作数据流向路径:数据——内核缓存区——磁盘
-
标准IO操作数据流向路径:数据——流缓存区——内核缓存区——磁盘