C语言文件随机访问fseek()和ftell()函数
文件随机访问是指在某个文件内直接读写任何给定位置数据的能力。通过获取与设定文件位置指示符可以实现这一功能,文件位置指示符指定了文件中的当前访问位置,该文件与一个给定的流关联。
下面的示例记录文件 messages.txt 中以 # 字符开头的所有行的位置:
表 1 列出了这些宏,以及在 ANSI C 定义它们之前,曾用于 origin 的传统取值。这些 offset 值可以是负的,但是,最终结果所获得的文件位置必须大于等于 0。
当处理文本流时(在可区分文本流和二进制流的系统上),应该使用通过调用函数 ftell()获得的值作为 offset 参数,并且让 origin 的值为 SEEK_SET。
函数 ftell()与 fseek()、fgetpos()与 fsetpos()并非互相兼容的,因为 fgetpos()和 fsetpos()用来指示文件位置的 fpos_t 对象,可以不是算术类型。
如果成功的话,fseek()会清除流的 EOF 标记并返回 0。非 0 的返回值表示发生错误。函数 rewind()将文件位置指示符设置成文件开头,并清除流的 EOF 与错误标记:
如果不考虑对错误标记的影响,那么调用 rewind(fp)等同于:
如果该文件已被以读写模式打开,那么在成功调用 fseek()、fsetpos()或 rewind()之后,就可以进行读写操作。
下面的例子使用一个索引表来存储文件中记录的位置。这个方法允许直接地访问需要被更新的记录。
在写操作之前的第二个 fseek()调用,可以用下面代码替换,以相对于之前的位置,移动文件指针:
获取当前文件位置
下面的函数返回当前文件的访问位置。当需要标记文件中的位置,以便以后返回到该位置时,可以使用下面的函数。long ftell(FILE*fp);ftell()返回 fp 流的文件位置。对一个二进制流来说,它与该位置之前的字符数量是相同的,也就是当前字符位置距离文件头部的偏差。当发生错误时,ftell()返回 -1。
int fgetpos(FILE*restrict fp,fpos_t*restrict ppos);fgetpos()将 fp 流的文件位置指示符写入 ppos 所引用的对象,该对象类型为 fpos_t。如果 fp 是一个宽字符导向流,那么 fgetpos()所存储的指示符也会包含流当前的转换状态。当发生错误时,fgetpos()返回非 0 值;当执行成功时,返回 0。
下面的示例记录文件 messages.txt 中以 # 字符开头的所有行的位置:
#define ARRAY_LEN 1000 long arrPos[ARRAY_LEN] = { 0L }; FILE *fp = fopen( "messages.txt", "r" ); if ( fp != NULL) { int i = 0, c1 = '\n', c2; while ( i < ARRAY_LEN && ( c2 = getc(fp) ) != EOF ) { if ( c1 == '\n' && c2 == '#' ) arrPos[i++] = ftell( fp ) - 1; c1 = c2; } /* ... */ }
设置文件访问位置
下面的函数修改文件位置指示符。int fsetpos(FILE*fp,const fpos_t*ppos);将文件位置指示符和转换状态设置成 ppos 所引用对象中存储的值。ppos 所引用对象内的这些值必须通过调用函数 fgetpos()才能获得。如果成功,fsetpos()返回 0,并清除该流的 EOF 标记。如果发生错误,则返回非 0 值。
int fseek(FILE*fp,long offset,int origin);将文件位置指示符设置为以参数 origin 作为参考点,offset 作为偏差。三种可能的参考点均被定义为宏值,参数 offset 指定位置只可能是相对这三种参考点中的一种。
表 1 列出了这些宏,以及在 ANSI C 定义它们之前,曾用于 origin 的传统取值。这些 offset 值可以是负的,但是,最终结果所获得的文件位置必须大于等于 0。
宏名称 | origin的传统取值 | 偏差相对于的参考点 |
SEEK_SET | 0 | 文件开头 |
SEEK_CUR | 1 | 当前文件位置 |
SEEK_END | 2 | 文件结尾 |
当处理文本流时(在可区分文本流和二进制流的系统上),应该使用通过调用函数 ftell()获得的值作为 offset 参数,并且让 origin 的值为 SEEK_SET。
函数 ftell()与 fseek()、fgetpos()与 fsetpos()并非互相兼容的,因为 fgetpos()和 fsetpos()用来指示文件位置的 fpos_t 对象,可以不是算术类型。
如果成功的话,fseek()会清除流的 EOF 标记并返回 0。非 0 的返回值表示发生错误。函数 rewind()将文件位置指示符设置成文件开头,并清除流的 EOF 与错误标记:
void rewind( FILE *fp );
如果不考虑对错误标记的影响,那么调用 rewind(fp)等同于:
(void)fseek( fp, 0L, SEEK_SET )
如果该文件已被以读写模式打开,那么在成功调用 fseek()、fsetpos()或 rewind()之后,就可以进行读写操作。
下面的例子使用一个索引表来存储文件中记录的位置。这个方法允许直接地访问需要被更新的记录。
// setNewName():在索引表中找关键字,并且更新文件中关键字所对应的记录 // 包含这些记录的文件,必须以“读写模式”打开;也就是采用模式字符串"r+b" // 参数:—指向被打开数据文件的指针;—关键字;—新名称 // 返回值:指向更新记录的指针,当未找到时,返回NULL // --------------------------------------------------------------- #include <stdio.h> #include <string.h> #include "Record.h" // 定义类型Record_t, IndexEntry_t: // typedef struct { long key; char name[32]; // /* ... */ } Record_t; // typedef struct { long key, pos; } IndexEntry_t; extern IndexEntry_t indexTab[]; // 索引表 extern int indexLen; // 表条目的数量 Record_t *setNewName( FILE *fp, long key, const char *newname ) { static Record_t record; int i; for ( i = 0; i < indexLen; ++i ) { if ( key == indexTab[i].key ) break; // 找到指定的键 } if ( i == indexLen ) return NULL; // 没有找到 // 将文件位置设定到该记录: if (fseek( fp, indexTab[i].pos, SEEK_SET ) != 0 ) return NULL; // 定位失败 // 读取记录: if ( fread( &record, sizeof(Record_t), 1, fp ) != 1 ) return NULL; // 读取错误 if ( key != record.key ) // 测试键值 return NULL; else { // 更新记录 size_t size = sizeof(record.name); strncpy( record.name, newname, size-1 ); record.name[size-1] = '\0'; if ( fseek( fp, indexTab[i].pos, SEEK_SET ) != 0 ) return NULL; // 设定文件位置出错 if ( fwrite( &record, sizeof(Record_t), 1, fp ) != 1 ) return NULL; // 写入文件出错 return &record; } }
在写操作之前的第二个 fseek()调用,可以用下面代码替换,以相对于之前的位置,移动文件指针:
if (fseek( fp, -(long)sizeof(Record_t), SEEK_CUR ) != 0 ) return NULL; // 设定文件位置出错
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算