文件概述
文件是指存储在外部存储器上的数据集合。更准确的来说,文件就是一组相关元素或数据的有序集合,而且每个集合都有一个符号化的指代,称这个符号化的指代为文件名。
文件类型
根据文件在外部存储器上的组织形式,文件可以分为ASCII文件和二进制文件。
ASCII文件(文本文件):以字符的方式进行存储,一个字符对应一个ASCII码,而一个ASCII码占用1字节。例如:整数12在内存中占用4个字节;如果按照ASCII码的方式存储,则占用2个字节。ASCII文件可以阅读,可以打印,但是它与内存数据交换时需要转换;
二进制文件:将内存中的数据按照其在内存中的存储形式原样输出并保存在文件中。二进制文件占用空间小,内存数据与磁盘数据交换时无需转换,可以节省外存空间和转换时间。但是二进制文件不可阅读、打印。
在C语言中,扩展名为.c的源文件是由字符构成,而扩展名由.obj目标文件和.exe可执行文件是由二进制符号构成的。其实:txt文件也是一个文本文件。
C如何操作文件——文件指针
C语言是通过文件指针变量的操作来实现对文件的具体访问。
文件的指针不是指向一段内存空间,而是指向描述有关这个文件的相关信息的一个文件信息结构体,该结构体定义在studio.h头文件中。当然,也无需了解有关此结构体的详细细节,只需要知道如何使用文件指针就行了。
声明文件指针变量的一般形式为:
FILE* 文件型指针变量名;
其中,FILE应为大写;它实际上是由系统定义的一个结构体,该结构体中包含了文件名、文件使用方式、当前位置等信息。
在stdio.h文件中,FILE的结构体定义为:
typedof atruct
{
int _fd; /* 文件号 */
int _cleft; /* 缓冲区剩下的字符 */
int _mode; /* 文件操作模式 */
char* _nextc; /* 下一个字符的位置 */
char* _buff; /* 文件缓冲区位置 */
}FILE;
文件缓冲区
由于文件存储在外存储器上,外存的数据读/写速度相对较慢,所以在对文件进行写/读操作时,系统会在内存中为文件的输入或输出开辟缓冲区。
当对文件进行输出时,系统首先把输出的数据填入为该文件开辟的缓冲区内,每当缓冲区被填满时,就把缓冲区中的内容一次性输出到对应的文件中;
当从某个文件输入数据时,首先将从输入文件中输入一批数据放入到该文件的内存缓冲区中,输入语句将从该缓冲区中依次读取数据;当该缓冲区的数据被读完时,将在从输入文件中输入一批数据到缓冲区。
下列情况会引发缓冲区的刷新:
缓冲区满时;
行缓冲区遇到回车时;
关闭文件;
使用特定函数刷新缓冲区。
通过提供缓冲区可以尽可能减少 read 和 write 调用的次数,从而降低执行 I/O 的时间。在 C 语言中,标准 I/O 库提供了 3 种类型的缓冲。
1) 全缓冲
在进行 I/O 操作时,只有当 I/O 缓冲区被填满时,才进行实际的 I/O 操作。对于驻留在磁盘上的文件,通常就是由标准 I/O 库来实施全缓冲的。在一个流上执行第一次 I/O 操作时,相关标准 I/O 函数通常调用 malloc 来获得需要使用的缓冲区。
在默认情况下,全缓冲的缓冲区可以由标准 I/O 例程自动刷新。当然,也可以通过调用 fflush 函数来强制刷新一个数据流。但需要特别注意的是,在标准 I/O 库方面,flush 函数意味着将缓冲区中的内容写到磁盘上;而在终端驱动程序方面,flush 函数则表示丢弃已存储在缓冲区中的数据。
2) 行缓冲
在这种情况下,只有当在输入和输出中遇到换行符时,才执行实际的 I/O 作。当然,因为标准 I/O 库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,即使还没有写一个换行符,也必须进行 I/O 操作。
很显然,它允许我们一次输出一个字符(如 fputc 函数),但只有在写完一行之后才进行实际 I/O 操作。当流涉及一个终端时,通常使用行缓冲。例如,使用最频繁的 printf 函数就是采用行缓冲,所以感觉不出缓冲的存在。
3) 不带缓冲
标准 IO 库不对字符进行缓冲存储。在一般情况下,标准错误流 stderr 通常也是不带缓冲的。
相对于这些系统默认的情况,也可以通过调用标准库函数 setbuf 和 setvbuf 来更改缓冲类型。函数 setbuf 和 setvbuf 将使得在打开文件后用户可以建立自己的文件缓冲区,而不使用由 fopen 函数打开文件所设定的默认缓冲区。函数 setbuf 和 setvbuf 的一般函数原型如下所示:
void setbuf(FILE *fp, char *buf);
int setvbuf(FILE *fp, char *buf, int mode, size_t size);
使用 setbuf 与 setvbuf 函数指定文件的缓冲区一定要在文件读写之前。一旦用户自己指定了文件的缓冲区,文件的读写就要在用户指定的缓冲区中进行,而不在系统默认指定的缓冲区中进行。
对于 setbuf 函数,当指定参数 buf 为 null 时,setbuf 函数将使得文件 I/O 不带缓冲。如下面的示例代码所示:
setbuf(fp, NULL);
对 setvbuf 函数来说,由于 setbuf 函数没有返回值,因此也无法确定 setbuf 函数的调用是否成功。在实际使用中,应该尽量使用 setvbuf 来替换 setbuf 函数,以验证流被成功地更改,返回值如果成功,则该函数返回 0,否则返回非零值。如下面的示例代码所示:
if (setvbuf(file, buf, buf ? _IOFBF : _IONBF, BUFSIZ) != 0) {
}
对 setvbuf 函数,则由 malloc 函数来分配缓冲区,参数 size 指明了缓冲区的长度(必须大于 0),而参数 mode 则表示缓冲的类型,取值如下所示:
_IOFBF | 全缓冲 |
_IOLBF | 行缓冲 |
_IONBF | 不缓冲 |
调大缓冲区
#include <stdio.h>
#include <sys/stat.h>
#include <sys/time.h>
#define BUF_SIZE 40960
#define LOOP_CNT 1000000
int main ()
{
int i = 0;
struct timeval start, end; // 计时用的结构
float timeuse; // 耗时,s为单位
char test_fmt[4108];
for(i = 0; i < 4108; i++){
test_fmt[i] = 'A';
} // 总共4108字节。
printf("循环%d条,数据总量%ld:\n", LOOP_CNT, (long)LOOP_CNT* 4108);
FILE *pFile1;
pFile1=fopen ("2.txt","w");
char buf[BUF_SIZE];
setvbuf ( pFile1 , buf, _IOFBF , BUF_SIZE );
printf("自定义缓冲区 = %d byte\n", BUF_SIZE);
gettimeofday(&start,NULL); // 开始计时
for (i = 0; i < LOOP_CNT; i++){
fprintf(pFile1, test_fmt, i);
}
fclose (pFile1);
gettimeofday(&end,NULL); // 结束计时
// 计算耗时
timeuse = 1000000*(end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
timeuse /= 1000000;
printf("自定义缓冲区写文件,用时:%f\n", timeuse);
return 0;
}
FILE结构里本身带有一个缓冲。而内核在操作IO的时候会还有一个缓冲区,内核将缓冲区写到磁盘也不是直接写,而是放到其IO队列中等待写入。加大文件缓冲区,也只是加大了用户态的缓冲区,而内核态缓冲区是没有变的,所以当用户态缓冲区超过4096一个页大小的时候,它从用户地址空间拷贝到内核地址空间时候,应该是切分了好几页,分别加入内核IO的队列中,准备写入到磁盘上。
文件的打开与关闭
C语言规定,任何文件在使用之前必须打开,使用之后必须关闭。对文件的操作都是通过标准函数来实现的。
文件的打开——fopen()函数
C语言用fopen()函数打开一个文件,其调用的一般形式为:
文件指针名 = fopen(文件名,文件的使用方式);
该函数可以通过对文件指针名的判断来对文件打开进行判断,如果文件指针名为NULL,则文件打开失败;否则打开成功。
文件的使用方式和含义如下表所示:
打开方式 | 含义 | 指定文件不存在时 | 指定文件存在时 |
r | 只读方式打开文本文件 | 出错 | 正常打开 |
w | 只写方式打开文本文件 | 建立新文件 | 文件原有内容丢失 |
a | 追加方式打开文本文件 | 建立新文件 | 在原有内容末尾追加 |
r+ | 读/写方式打开文本文件 | 出错 | 正常打开 |
w+ | 读/写方式创建新的文本文件 | 建立新文件 | 文件原有内容丢失 |
a+ | 读/追加方式建立新的文本文件 | 建立新文件 | 在原有内容末尾追加 |
rb | 只读方式打开二进制文件 | 出错 | 正常打开 |
wb | 只写方式打开二进制文件 | 建立新文件 | 文件原有内容丢失 |
ab | 追加方式打开二进制文件 | 建立新文件 | 在原有内容末尾添加 |
rb+ | 读/写方式打开二进制文件 | 出错 | 正常打开 |
wb+ | 读/写方式创建新的二进制文件 | 建立新文件 | 文件原有内容丢失 |
ab+ | 读/追加方式创建新的二进制文件 | 建立新文件 | 在原有内容末尾追加 |
高版本的VS编译器可能会认为fopen()函数不安全,会强制要求使用fopen_s()函数来代替。
fopen_s()函数调用的一般形式为:
errno_t err;
err = fopen_s(指向文件指针的指针,文件名,文件的使用方式);
err = fopen_s(&fp,"d:\\1.txt","r");
这个函数的使用有两点注意:
该函数有返回值,如果打开文件成功,函数返回值为0;否则返回值非0;
该函数的第一个参数时指向文件指针的指针,也就是说,需要传递文件指针的地址。
文件的关闭——fclose()函数
在程序中,当对一个文件的操作使用完毕后,应将其关闭,断开文件指针与该文件之间的联系,防止文件遭到其他操作的破坏。C语言用fclose()函数打开一个文件,其调用的一般形式为:
fclose(文件指针);
该函数有返回值,如果关闭文件成功,函数返回值为0;否则返回值非0。
文件的顺序读/写
字符读/写函数fgetc()和fputc()
fgetc()函数的功能是从指定的文件中读取一个字符,其调用的形式为:
字符变量 = fgetc (文件指针);
如果在执行fgetc()函数时遇到文件结束符,函数会返回一个文件结束符标志EOF(-1)。
fputc()函数的功能是把一个字符写入指定的文件中,其一般调用的格式为:
fput(字符,文件指针);
# include <stdio.h>
# include <string.h>
int main( )
{
FILE* fp1, *fp2;
char c;
fp1 = fopen("d:\\1.txt", "w");
if (fp1 == NULL){
printf("文件打开失败!\n");
exit(0);
}
c = getchar();
while (c != '\n') {
fputc(c, fp1);
c = getchar();
}
fclose(fp1);
fp2 = fopen("d:\\1.txt", "r");
if (fp2 == NULL) {
printf("文件打开失败!\n");
exit(0);
}
c = fgetc(fp2);
while (c != EOF) {
printf("%c", c);
c = fgetc(fp2);
}
printf("\n");
fclose(fp2);
return 0;
}
字符串读/写函数fgets()和fputs()
fgets()函数的功能是从指定的文件中读取一个字符串,其调用的形式为:
fgets(字符数组名,n,文件指针);
其中,n是一个正整数,表示从文件中读出的字符串不超过n-1个字符。在读入一个字符串后加上字符串结束标志'\0'。
如果在执行fgets()函数时如果文件内的字符串读取完毕,函数会返回0。
fputs()函数的功能是把一个字符串写入指定的文件中,其一般调用的格式为:
fputs(字符串,文件指针);
其中,字符串可以是字符串常量、字符数组、字符指针变量。
# include <stdio.h>
# include <string.h>
int main( )
{
FILE* fp1, *fp2;
char s[30];
fp1 = fopen("d:\\1.txt", "w");
if (fp1 == NULL){
printf("文件打开失败!\n");
exit(0);
}
gets(s);
while (strlen(s) > 0) {
fputs(s,fp1);
gets(s);
}
fclose(fp1);
fp2 = fopen("d:\\1.txt", "r");
if (fp2 == NULL){
printf("文件打开失败!\n");
exit(0);
}
while (fgets(s, 11, fp2) != 0) {
printf("%s", s);
}
printf("\n");
fclose(fp2);
return 0;
}
数据块读/写函数fread()和fwrite()
读数据块函数fread(),其调用的一般形式为:
fread(buf,size,n,文件指针);
fread()函数的功能是从文件中读取字节长度为size的n个数据,并存放到buf指向的内存地址中去。
函数的返回值为实际读出的数据项个数。比如:
fread(fa,4,5,fp);
其意义是从fp所指向的文件中,每次读4个字节长度(int)送入到fa指向的内存地址中去,连续读5次。也就是说,读5个int类型的数据到fa指向的内存中。
写数据块函数fwrite(),其调用的一般形式为:
fwrite(buf,size,n,文件指针);
fread()函数的功能是将buf中存放的size*n个字节的数据输出到文件指针所指向的文件中去。
函数的返回值为实际写入的数据项个数。
fread()和fwrite()函数一般适用于二进制文件,它们是按数据块的大小来处理输入/输出的。
# include <stdio.h>
int main( )
{
FILE *fp1;
int buffer[] = {1, 2, 3, 4};
fp1 = fopen("d:\\1.txt", "wb");
if (fp1 == NULL){
printf("文件打开失败!\n");
exit(0);
}
fwrite(buffer, sizeof(int), 4, fp1);
fclose (fp1);
return 0;
}
==========================================================
# include <stdio.h>
int main( )
{
FILE *fp1;
int buffer[4] ;
int i;
fp1 = fopen("d:\\1.txt", "rb");
if (fp1 == NULL){
printf("文件打开失败!\n");
exit(0);
}
fread(buffer,sizeof(int),4,fp1);
for (i = 0; i<4; i++){
printf("%d\n", buffer[i]);
}
fclose (fp1);
return 0;
}
读写结构体
# include <stdio.h>
typedef struct
{
int age;
char name[30];
}people;
int main( )
{
FILE *fp;
int i;
people per[3];
per[0].age = 20;
strcpy(per[0].name, "li");
per[1].age = 10;
strcpy(per[1].name, "wang");
per[2].age = 10;
strcpy(per[2].name, "zhang");
if((fp = fopen ("myfile.txt", "wb"))==NULL)
{
printf("cant open the file");
exit(0);
}
for(i=0;i<3;i++)
{
if(fwrite(&per[i],sizeof(people),1,fp)!=1){
printf("file write error\n");
}
}
fclose (fp);
return 0;
}
===============================================================
# include <stdio.h>
typedef struct
{
int age;
char name[30];
}people;
int main( )
{
FILE *fp;
int i;
people per;
if ((fp = fopen ("myfile.txt", "rb")) == NULL)
{
printf("cant open the file");
exit(0);
}
while (fread(&per,sizeof(people),1,fp) ==1 ) //如果读到数据,就显示;否则退出
{
printf("%d %s\n",per.age,per.name);
}
fclose (fp);
return 0;
}
格式化读/写函数fscanf()和fprintf()
格式化读/写函数与标准的格式输入/输出函数功能相同,只不过它们的读/写对象不是键盘和显示器,而是文件。fscanf()和fprintf()函数只适用于ASCII码文件的读/写。
两个函数的格式如下:
fscanf(文件指针,格式字符串,输入列表);
fprintf(文件指针,格式字符串,输出列表);
fscanf()和fprintf()函数对文件进行读/写,使用方便,容易理解。但由于在输入时需要将ASCII码转换为二进制格式,在输出时又要将二进制格式转换为字符,花费时间较长,所以在内存与磁盘交换数据频繁的时候,最好不要用这两个函数。
int main( )
{
FILE *fp=NULL;
char buff[255];
fp=fopen("tmp/test.txt","r");
fprintf(fp, "This is testing for fprintf...\n");//写文件
fscanf(fp,"%s", buff); //读文件
printf("1: %s\n", buff );
fclose(fp);
fp=NULL;
}
文件定位与文件的随机读/写
在C语言中,打开文件时,文件指针指向文件头,即文件的起始位置。在读写文件时,需要从文件头开始,每次读写完一个数据后,文件指针会自动指向下一个数据的位置。但有时不想从文件头开始读取文件,而是读取文件中某个位置的数据。这时,系统提供了定位到某个数据存储位置的函数。
文件头定位函数rewind()
rewind()函数用于把文件指针移动到文件首部,其调用的一般形式为:
rewind(文件指针);
如,把一个文件的内容显示在屏幕上,并同时复制到另一个文件。
# include <stdio.h>
int main( )
{
FILE *fp1, *fp2;
fp1 = fopen("f.txt", "r"); // 源文件
fp2 = fopen("f2.txt", "w"); // 复制到file2.c
while(!feof(fp1)) {
putchar(fgetc(fp1)); // 显示到屏幕上
}
rewind(fp1); // fp回到开始位置
while(!feof(fp1)) {
fputc(fgetc(fp1), fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
当前读/写位置函数ftell()
ftell()函数用于确定文件指针的当前读/写位置,其调用的一般形式为:
ftell(文件指针);
此函数有返回值,若成功定位,则返回当前位置;否则返回-1。
# include <stdio.h>
int main( )
{
FILE *stream;
stream=fopen("file.txt","w+");
fprintf(stream,"This is a test");
printf("The file pointer is at byte %ld\n",ftell(stream));
fclose(stream);
return 0;
}
求文件有多少个字节
# include <stdio.h>
int main( )
{
FILE *myf;
long f1;
myf=fopen("d://1.txt","rb");
fseek(myf,0,SEEK_END); //定位到文件末尾
f1=ftell(myf);
fclose(myf);
printf("%d\n",f1);
return 0;
}
随机定位函数fseek()
fseek()函数用于将文件指针移动到某个确定的位置,其调用的一般形式为:
fseek(文件指针,位移量,起始点);
此函数有返回值,若成功移动,则返回当前位置;否则返回-1。
其中:位移量指从起始点向前移动的字节数,大多数C版本要求该位移量为long型数据;起始点有三种选择,具体的含义见下表:
起始点 | 表示符号 | 数字表示 |
文件首 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件尾 | SEEK_END | 2 |
例如,将指针位置移动到距离文件开头100字节处:
fseek(fp,100L,0)
注意:fseek()函数一般用于二进制文件,因为文本文件计算位置往往比较混乱,容易发生错误。
# include <stdio.h>
int main( )
{
FILE * fp = fopen("f.txt", "r+");
if (fp == NULL) {
printf("file error\n");
exit(1);
}
fseek(fp, 20, SEEK_SET);//光标移到文件开始起第二个字节处。
fwrite("yun", 1, 3, fp); //文件内写入内容yun
fclose(fp);
return 0;
}
文件检测函数
C语言还提供了一些检测函数,用于在文件打开、关闭以及读/写操作过程中对有可能会发生的一些情况进行检测。
文件结束检测函数feof()
feof()函数用于判断文件是否处于文件结束为止,其调用的一般格式为:
feof(文件指针);
该函数有返回值,如果文件结束,函数的返回值为1;否则返回值为0。
#include <stdio.h>
int main ()
{
FILE *fp;
int c;
fp = fopen("file.txt","r");
if(fp == NULL)
{
perror("打开文件时发生错误");
return(-1);
}
while(1)
{
c = fgetc(fp);
if( feof(fp) )
{
break ;
}
printf("%c", c);
}
fclose(fp);
return(0);
}
读/写文件出错检测函数ferror()
ferror()函数用于检查文件在使用各种读/写函数时是否出错,其调用的一般格式为:
ferror(文件指针);
该函数有返回值,如果没有错误,函数的返回值为0;否则返回值非0。
#include <stdio.h>
int main()
{
FILE *fp;
char c;
fp = fopen("file.txt", "w");
c = fgetc(fp);
if( ferror(fp) )
{
printf("读取文件:file.txt 时发生错误\n");
}
clearerr(fp);
if( ferror(fp) )
{
printf("读取文件:file.txt 时发生错误\n");
}
fclose(fp);
return(0);
}
文件出错标志清除函数clearerr()
clearerr()函数用于清除出错标志,其调用的一般格式为:
clearerr(文件指针);
在ferror()函数值为非0时,在调用此函数后,ferror()函数的值变为0。