数组和链表如何读入读出文件
本文将分析数组和链表在文件读写中的原理、方法
文章目录
1.基本原理分析
数据文件可分为文本文件和二进制文件。所以我们在读取和写入数据时就有文本格式和二进制格式两种方式
文本格式:以人类可读的字符形式存储数据
二进制格式:以原始二进制形式存储数据
写入/读取数据本质上是一个逆过程,一定要格式一致(比如你写入了数组大小,读取时一定要先读取数组大小)
文本格式读写分析
写入过程:
将数组中的元素转换为文本格式写入文件,使文件内容表现为文本格式
文本格式写入时,需要将二进制数据转换为字符串形式
- 步骤:
- fprintf将整数转换为可读的字符串形式
- 每个数字后加空格分隔,便于读取
void write_array_text(const char* filename, int arr[], int size) {
FILE* file = fopen(filename, "w"); // 文本写模式
for (int i = 0; i < size; i++) {
fprintf(file, "%d ", arr[i]); // 格式化写入
}
fclose(file);
}
读取过程:
从文本文件中读取字符数据,解析为对应的数据类型,存储到数组中
由于文件中没有存储数组大小的信息,所以我们需要先统计数组大小,必须首先确定元素数量才能分配正确大小的内存,rewind()将文件指针重置到开头,重新读取数据
- 步骤:
- 统计元素个数
- 文件指针回到开头
- 重新读取数据
int* read_array_text(const char* filename, int* size) {
// 第一步:统计元素个数
int count = 0;
while (fscanf(file, "%d", &temp) == 1) count++;
// 第二步:文件指针回到开头
rewind(file);
// 第三步:重新读取并存储
int* arr = malloc(count * sizeof(int));
for (int i = 0; i < count; i++) {
fscanf(file, "%d", &arr[i]);
}
}
二进制格式读写分析
写入过程:
将数组在内存中原始二进制内容写入到文件,使文件内容表现为二进制数据
- 步骤:
- 写入数组大小
- 写入数组数据
fwrite直接写入内存中的原始字节
第一个写入数组大小,为读取时提供信息
第二个写入整个数组的连续内存块
文件结构:[size][element1][element2]…[elementN]
void write_array_binary(const char* filename, int arr[], int size) {
FILE* file = fopen(filename, "wb"); // 二进制写模式
// 第一步:写入数组大小
fwrite(&size, sizeof(int), 1, file);
// 第二步:写入数组数据
fwrite(arr, sizeof(int), size, file);
fclose(file);
}
读取过程:
从二进制文件中直接读取原始数据块(二进制),直接加载到数组内存中
- 步骤:
- 读取数组大小
- 根据大小分配内存
- 一次性读取所有数据
二进制文件可以直接从文件头部获取文件大小信息
一次性分配足够内存
单次读取整个数组,效率极高
int* read_array_binary(const char* filename, int* size) {
FILE* file = fopen(filename, "rb"); // 二进制读模式
// 首先读取数组大小
fread(size, sizeof(int), 1, file);
// 根据大小分配内存
int* arr = malloc(*size * sizeof(int));
// 一次性读取所有数据
fread(arr, sizeof(int), *size, file);
fclose(file);
return arr;
}
3.链表的文件读写分析
链表与数组的最大区别在于内存布局:链表节点在内存中不连续,每个节点都是动态分配的。这意味着不能像数组那样直接整体读写,必须逐个节点处理。
无论文本格式还是二进制格式,链表的文件操作本质上都是链表重建过程。
文本格式读写分析
写入过程:
将链表的数据转换为文本格式写入文件,使文件内容表现为文本(字符)格式
- 步骤:
- 遍历链表,逐个节点写入数据
- 每行存储一个节点的数据值
不存储指针信息(指针在程序重启后无效)
void write_list_text(const char* filename, Node* head) {
FILE* file = fopen(filename, "w");
Node* current = head;
while (current != NULL) {
fprintf(file, "%d\n", current->data); // 每行一个数据
current = current->next;
}
fclose(file);
}
读取过程:
从文本文件中读取字符数据,解析为对应的数据类型,存储到链表中
读取过程就是重建链表的过程
- 步骤:
-
读取每个数据值
-
动态创建新节点
-
使用appendNode将节点链接成链表
(这里appendNode起到的作用是在链表末尾添加新节点,属于链表的基本功能实现,所以把它封装成函数)
-
新建的链表结构与原链表数据相同,但内存地址完全不同
我们先给出appendNode函数的定义
void appendNode(Node**head,int data){
//创建新节点p
Node*p=(Node*)malloc(sizeof(Node));
p->data=data;
p->next=NULL;
//如果链表为空,修改指针head指向p
//(使用二级指针的原因,我们需要修改指针的值)
if(*head==NULL) {
*head=p;
return;
}
//遍历到链表末尾
Node*current=*head;
while(current->next!=NULL){
current=current->next;
}
current->next=p;
}
接下来是重建链表的过程
Node* read_list_text(const char* filename) {
FILE* file = fopen(filename, "r");
Node* head = NULL;
int data;
while (fscanf(file, "%d", &data) == 1) {
appendNode(&head, data); // 动态创建节点并链接
}
fclose(file);
return head;
}
二进制格式读写分析
写入过程:
将链表在内存中原始二进制内容写入到文件,使文件内容表现为二进制数据
- 步骤:
-
计算链表长度
-
写入节点数量
-
写入所有节点数据
为什么需要计算长度?
链表没有预知的长度信息
必须遍历统计才能知道要写入多少数据
长度信息对读取时重建链表至关重要
void write_list_binary(const char* filename, Node* head) {
FILE* file = fopen(filename, "wb");
// 第一步:计算链表长度
int count = 0;
Node* current = head;
while (current != NULL) {
count++;
current = current->next;
}
// 第二步:写入节点数量
fwrite(&count, sizeof(int), 1, file);
// 第三步:写入所有节点数据
current = head;
while (current != NULL) {
fwrite(&(current->data), sizeof(int), 1, file);
current = current->next;
}
fclose(file);
}
读取过程:
从二进制文件中直接读取原始数据块(二进制),直接加载到节点内存中
二进制链表重建
- 步骤:
-
读取节点数量,知道要创建多少个节点
-
循环读取每个数据值
-
动态创建节点并链接
Node* read_list_binary(const char* filename) {
FILE* file = fopen(filename, "rb");
// 读取节点数量
int count;
fread(&count, sizeof(int), 1, file);
Node* head = NULL;
int data;
// 根据数量创建对应节点
for (int i = 0; i < count; i++) {
fread(&data, sizeof(int), 1, file);
appendNode(&head, data);
}
fclose(file);
return head;
}
4.总结
数组操作要点
- 文本格式:循环写入每个元素,读取时先统计数量再分配内存
- 二进制格式:先写入数组大小,再写入整个数据块;读取时先获取大小再一次性读取
链表操作要点
-
核心原则:不能整体读写,必须逐个节点处理
-
文本格式:每行存储一个节点数据,读取时逐行重建链表
-
二进制格式:先计算并写入节点数量,再写入所有节点数据;读取时按数量循环重建
更多推荐
所有评论(0)