本文将分析数组和链表在文件读写中的原理、方法

1.基本原理分析

数据文件可分为文本文件和二进制文件。所以我们在读取和写入数据时就有文本格式和二进制格式两种方式

  • 文本格式:以人类可读的字符形式存储数据

  • 二进制格式:以原始二进制形式存储数据

写入/读取数据本质上是一个逆过程,一定要格式一致(比如你写入了数组大小,读取时一定要先读取数组大小)


文本格式读写分析

写入过程:

将数组中的元素转换为文本格式写入文件,使文件内容表现为文本格式

文本格式写入时,需要将二进制数据转换为字符串形式

  • 步骤:
  1. fprintf将整数转换为可读的字符串形式
  2. 每个数字后加空格分隔,便于读取
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()将文件指针重置到开头,重新读取数据

  • 步骤:
  1. 统计元素个数
  2. 文件指针回到开头
  3. 重新读取数据
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]);
    }
}

二进制格式读写分析

写入过程:

将数组在内存中原始二进制内容写入到文件,使文件内容表现为二进制数据

  • 步骤:
  1. 写入数组大小
  2. 写入数组数据

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);
}

读取过程:

从二进制文件中直接读取原始数据块(二进制),直接加载到数组内存中

  • 步骤:
  1. 读取数组大小
  2. 根据大小分配内存
  3. 一次性读取所有数据

二进制文件可以直接从文件头部获取文件大小信息
一次性分配足够内存
单次读取整个数组,效率极高

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.链表的文件读写分析

链表与数组的最大区别在于内存布局:链表节点在内存中不连续,每个节点都是动态分配的。这意味着不能像数组那样直接整体读写,必须逐个节点处理。

无论文本格式还是二进制格式,链表的文件操作本质上都是链表重建过程。


文本格式读写分析

写入过程:

将链表的数据转换为文本格式写入文件,使文件内容表现为文本(字符)格式

  • 步骤:
  1. 遍历链表,逐个节点写入数据
  2. 每行存储一个节点的数据值
    不存储指针信息(指针在程序重启后无效)
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);
}

读取过程:

从文本文件中读取字符数据,解析为对应的数据类型,存储到链表中

读取过程就是重建链表的过程

  • 步骤:
  1. 读取每个数据值

  2. 动态创建新节点

  3. 使用appendNode将节点链接成链表

    (这里appendNode起到的作用是在链表末尾添加新节点,属于链表的基本功能实现,所以把它封装成函数)

  4. 新建的链表结构与原链表数据相同,但内存地址完全不同

我们先给出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;
}

二进制格式读写分析

写入过程:

将链表在内存中原始二进制内容写入到文件,使文件内容表现为二进制数据

  • 步骤:
  1. 计算链表长度

  2. 写入节点数量

  3. 写入所有节点数据

为什么需要计算长度?

链表没有预知的长度信息
必须遍历统计才能知道要写入多少数据
长度信息对读取时重建链表至关重要

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);
}

读取过程:

从二进制文件中直接读取原始数据块(二进制),直接加载到节点内存中

二进制链表重建

  • 步骤:
  1. 读取节点数量,知道要创建多少个节点

  2. 循环读取每个数据值

  3. 动态创建节点并链接

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.总结

数组操作要点

  • 文本格式:循环写入每个元素,读取时先统计数量再分配内存
  • 二进制格式:先写入数组大小,再写入整个数据块;读取时先获取大小再一次性读取

链表操作要点

  • 核心原则:不能整体读写,必须逐个节点处理

  • 文本格式:每行存储一个节点数据,读取时逐行重建链表

  • 二进制格式:先计算并写入节点数量,再写入所有节点数据;读取时按数量循环重建

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐