Linux系统下,通过编程对文件进行操作的方式有两种机制:文件描述符和文件流
1.文件描述符和文件流的区别:
文件描述符的类型为int,文件流的类型为FILE*(文件指针)。
文件描述符的操作更底层,文件流的操作更高级且更丰富。
文件流是基于文件描述符来实现的,所以可以从文件流中提取并操作文件描述符,比如“int fileno(FILE*); fileno(file_stream)”。
对于一个文件,如果涉及到格式化的输入/输出,以及面向字符或行的输入/输出,更推荐使用文件流进行操作。
2.文件流的定向设置
文件流的定向决定了一个I/O操作一次能操作多少个字节,是单字节(字节定向)还是多字节(宽定向)。
fwide()用于设置流的定向
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
--返回值:若流是宽定向,返回正数;若流的字节定向,返回负数;若流是未定向的,返回0
mode为负数,fwide将指定文件流可以进行单字节I/O操作(字节定向)
mode为正数,fwide将指定文件流可以进行多字节I/O操作(宽定向)
mode为0,不设置定向,但返回该流定向的值
*fwide不改变已定向的流
代码样例:
#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
void try_read(FILE* fp)
{
int c = fgetc(fp);
if(c == EOF) puts("narrow character read failed");
else printf("narrow character read '%c'\n", c);
wint_t wc = fgetwc(fp);
if(wc == WEOF) puts("wide character read failed");
else printf("wide character read '%lc'\n", wc);
}
void show(int n)
{
if(n == 0) puts("no orientation");
else if (n < 0) puts("narrow orientation");
else puts("wide orientation");
}
int main(void)
{
FILE* fp = fopen("test.txt","r");
if (!fp) {
perror("fopen() failed");
return EXIT_FAILURE;
}
// A newly opened stream has no orientation.
show(fwide(fp, 0));
// Establish byte orientation.
show(fwide(fp, -1));
try_read(fp);
// Only freopen() can reset stream orientation.
if (freopen("test.txt","r",fp) == NULL)
{
perror("freopen() failed");
return EXIT_FAILURE;
}
// Establish wide orientation.
show(fwide(fp, 1));
try_read(fp);
fclose(fp);
}
运行结果:
no orientation
narrow orientation
narrow character read 'a'
wide character read failed
wide orientation
narrow character read failed
wide character read 'a'
3.缓冲区大小设置
Linux内核操作文件会使用高速缓冲区。比如write操作, 进程把数据写到缓冲区, 然后内核把数据从缓冲区写到磁盘文件。
当进程不断写入数据时,内核可以等缓冲区满了再一次性往磁盘写入,这样可以提高性能。
流程图如下:
缓冲分三种模式:
(1)全缓冲,写满标准I/O缓冲区后才进行I/O操作, 例如磁盘文件(非交互式设备)的I/O操作
(2)行缓冲,在输入/输出中遇到换行符时才进行I/O操作,例如在终端进行I/O操作
(3)无缓冲,写入字符后立马进行读操作,例如标准错误流stderr
刷新缓冲区函数:
include <stdio.h>
int fflush( FILE *stream );
变更缓冲的函数--setbuf()/setvbuf()
void setbuf( FILE *restrict stream, char *restrict buffer);
--stream为文件流指针,buf为缓冲区首地址
若成功则返回0,若出错则为非0
int setvbuf( FILE *restrict stream, char *restrict buffer,
int mode, size_t size );
--stream为文件流指针,buf为缓冲区首地址,mode为缓冲区类型,size为缓冲区内字节的数量
--mode参数如下:
_IOFBF:全缓冲模式
_IOLBF:行缓冲模式
_IONBF:无缓冲模式
若成功则返回0,若出错则为非0
代码样例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
int main(void)
{
char buff[1024];
memset(buff, '\0', sizeof(buff));
struct stat file_stats;
FILE* fp = fopen("test.txt", "w+");
fstat(fileno(fp), &file_stats);
printf("before setvbuf: file buf size is %ld\n", file_stats.st_blksize);
if(setvbuf(fp, buff, _IOFBF, 1024) != 0) {
perror("setvbuf failed");
return EXIT_FAILURE;
}
fclose(fp);
return EXIT_SUCCESS;
}
4.文件流的打开和关闭
常用的open函数--fopen()/freopen()/fdopen()
fopen():打开路径名为pathname的指定文件
freopen():在一个指定的流上打开文件。若该流已经打开,先关闭该流。若该流已设置定向类型,则先清除该定向
fdopen():打开一个已有的文件描述符。该文件描述符的来源:open、dup、dup2、fcntl、pipe、socket、socketpair、accept...
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);
--若成功,返回文件指针FILE*;若出错,返回NULL
三个函数中的type参数,有下面这些取值:
关闭一个打开的流--fclose()
#include <stdio.h>
int fclose(FILE *fp)
5.单个字符操作
从文件流中一次读一个字符
#include <stdio.h>
int getc(FILE *fp)
int fgetc(FILE *fp)
int getchar(void) /*等同于getc(stdin)*/
上面的函数在出错或到达文件末尾时,都返回同样的值,为了区分是出错还是到达文件末尾,可以调用ferror(FILE *fp)/feof(FILE *fp)。
从流中读取数据以后,可以调用ungetc(int c, FILE *fp)将字符再送回到流中。
对应于getc()的操作,在文件流中一次写一个字符
#include <stdio.h>
int putc(int c, FILE *fp)
int fputc(int c, FILE *fp)
int putchar(int c) /*等同于putc(c, stdout)*/
6.行操作
一次从流中读出一行
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp)
char *gets(char *buf)
--若成功,返回buf;若出错,返回NULL
一次往流中写入一行
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp)
int puts(const char *str)
7.二进制操作
一次读/写一个二进制数组或者结构体
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)
8.其他流操作
返回文件流中文件指针的位置:
long ftell( FILE *stream );
设置文件流中文件指针位置
int fseek( FILE * stream, long offset, int origin );
将文件流的读写位置移至文件开头
void rewind( FILE *stream );
删除指定文件
int remove( const char *fname );
格式化流
int scanf( const char *restrict format, ... );
int fscanf( FILE *restrict stream, const char *restrict format, ... );
int sscanf( const char *restrict buffer, const char *restrict format, ... );
int printf( const char *restrict format, ... );
int fprintf( FILE *restrict stream, const char *restrict format, ... );
int sprintf( char *restrict buffer, const char *restrict format, ... );
代码样例:
Demo1:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const char* fname = "test.txt";
FILE* fp = fopen(fname, "w+");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
fputs("Hello, world!\n", fp);
rewind(fp);
int c; // note: int, not char, required to handle EOF
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
if (ferror(fp)) {
puts("I/O error when reading");
} else if (feof(fp)) {
puts("End of file reached successfully");
}
fclose(fp);
remove(fname);
return EXIT_SUCCESS;
}
运行结果:
Hello, world!
End of file reached successfully
Demo2:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
#define SIZE 5
double A[SIZE] = {1.0, 2.0, 3.0, 4.0, 5.0};
double B[SIZE];
FILE * fp = fopen("test.bin", "wb");
fwrite(A, sizeof(double), SIZE, fp);
fclose (fp);
fp = fopen("test.bin", "rb");
fseek(fp, sizeof(double) * 3L, SEEK_SET);
fread(B, sizeof(double), 1, fp);
printf("file point position is : %ld\n", ftell(fp));
printf("B[0] == %.1f\n", B[0]);
memset(B, 0.0, sizeof(B));
fseek(fp, sizeof(double) * 4L, SEEK_SET);
fread(B, sizeof(double), 1, fp);
printf("file point position is : %ld\n", ftell(fp));
printf("B[0] == %.1f\n", B[0]);
fclose(fp);
return EXIT_SUCCESS;
}
运行结果:
file point position is : 32
B[0] == 4.0
file point position is : 40
B[0] == 5.0
参考教程:
《UNIX环境高级编程第3版》
https://en.cppreference.com/w/c/io/