C++文件操作实践:分块写入与读取数据

一、引言

在开发应用软件时,文件操作是不可或缺的一部分。为了高效地处理大文件或需要对特定位置进行随机访问的数据,我们通常会将数据分块处理。这篇文章将通过一个简单的例子向大家介绍如何使用C++语言实现数据的分块写入和读取。我们将创建结构体表示学生信息,并演示如何将这些信息写入二进制文件以及从文件中读回。

二、代码解析

2.1 定义数据结构

首先,定义了一个Student结构体用来保存学生的姓名、年龄、分数和等级等信息。构造函数允许我们以更简便的方式初始化这个结构体实例。

struct Student {
    char name[32];      // 学生姓名
    int age;            // 年龄
    float score;        // 分数
    char grade;         // 等级

    Student(const char* n = "", int a = 0, float s = 0.0f, char g = 'N') {
        strncpy(name, n, sizeof(name) - 1);
        name[sizeof(name) - 1] = '\0';  // 确保字符串结束
        age = a;
        score = s;
        grade = g;
    }
};

2.2 实现分块写入与读取

接着,实现了两个模板函数writeDataInBlocksreadFile用于处理不同类型的对象分块写入和读取。这里的关键点在于根据数据大小计算出完整的块数量和剩余字节,然后使用文件流的seekpseekg方法定位到正确的起始位置,再进行相应的读写操作。

template<typename T>
void writeDataInBlocks(const T* data, size_t elementCount, const string& filename, size_t startBlock = 0) { ... }

template<typename T>
int readFile(const string& filename, size_t elementCount, T* data, size_t startBlock = 0) { ... }

2.3 主函数中的测试用例

最后,在main函数中,我们分别创建了字符串和学生数组作为测试数据,调用上述函数完成数据的写入和读取,并打印输出读取的结果以验证正确性。

int main() {
    // 测试字符串数据...
    
    // 创建学生数据示例并写入文件
    Student students[] = {
        Student("Alicffffffffffffdfffffffffffe", 20, 95.5f, 'A'),
        Student("Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbob", 22, 88.0f, 'B'),
        Student("Chbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbarlie", 21, 92.5f, 'A')
    };
    size_t studentCount = sizeof(students) / sizeof(Student);
    
    // 将整个学生数组写入文件,自动分块
    writeDataInBlocks(students, studentCount, "students.bin");

    // 读取学生数据并打印...
}

三、运行结果展示

编译并执行这段代码后,程序会创建名为multi_block.binstudents.bin的文件,前者包含了两个字符串,后者则包含三个学生的信息。通过终端输出可以看到,我们成功地将信息写入文件,并准确无误地读取了回来。

四、总结

在C++中,使用char*(或const char*)而不是string确实各有优缺点。
优点:

  1. 内存布局确定性
    • char*是连续的内存块,内存布局是可预测的
    • string的内部实现可能因编译器不同而不同,可能包含额外的成员(如长度、容量等)
  2. 二进制兼容性
    • char*更适合二进制I/O,因为它就是原始的字节序列
    • string可能包含额外的元数据,直接写入会导致问题
  3. 性能考虑
    • char*是最基本的内存表示,没有额外开销
    • string需要管理动态内存,有构造、析构等开销
  4. 跨平台/跨语言兼容性
    • char*是C风格的字符串表示,几乎所有编程语言都支持
    • string的实现在不同编译器间可能不兼容

缺点:

  1. 安全性较低

    • 需要手动管理内存
    • 容易发生缓冲区溢出
    • 需要手动处理字符串结束符’\0’
  2. 使用不便

    • 没有内置的字符串操作函数
    • 需要手动计算长度
    • 字符串拼接等操作更复杂
      在代码中使用char*的具体原因:
// 使用char*的例子
const char* strData = "Hello, World!";
size_t strLen = strlen(strData) + 1; // +1 for null terminator
writeDataInBlocks(strData, strLen, "string_output.bin");

使用char*主要是因为在做二进制I/O操作,需要:

  1. 精确控制内存布局
  2. 确保数据的二进制表示是可预测的
  3. 避免写入string的额外元数据

建议:

  • 如果是普通的字符串处理,建议使用string,更安全、更方便
  • 如果是底层I/O、内存操作、跨语言接口等场景,使用char*更合适
  • 在现代C++中,通常推荐使用string_view作为一个折中方案,它提供了类似char*的性能优势,同时又有string的安全性

writeDataInBlocksreadFile是按块写入的,并自动向下一块拓展,如果需要寻找没有使用的块并写入需要自己实现。例如模仿FAT文件系统。

#include <iostream>
#include <fstream>
#include <string>
#include <cstring> // For memcpy
using namespace std;

const size_t BLOCK_SIZE = 128; // 1024 bits / 8 bits per byte

// 定义学生信息结构体
struct Student {
    char name[32];      // 学生姓名
    int age;            // 年龄
    float score;        // 分数
    char grade;         // 等级

    // 构造函数,方便初始化
    Student(const char* n = "", int a = 0, float s = 0.0f, char g = 'N') {
        strncpy(name, n, sizeof(name) - 1);
        name[sizeof(name) - 1] = '\0';  // 确保字符串结束
        age = a;
        score = s;
        grade = g;
    }
};

template<typename T>
void writeDataInBlocks(const T* data, size_t elementCount, const string& filename, size_t startBlock = 0) {
    ofstream outFile(filename, ios::binary | ios::in | ios::out);
    if (!outFile) {
        // If file doesn't exist, create it
        outFile.close();
        outFile.open(filename, ios::binary);
    }

    if (!outFile) {
        cerr << "Error opening file for writing." << endl;
        return;
    }

    size_t totalBytes = elementCount * sizeof(T);
    size_t fullBlocks = totalBytes / BLOCK_SIZE;
    size_t remainingBytes = totalBytes % BLOCK_SIZE;

    // Seek to the starting block position
    outFile.seekp(startBlock * BLOCK_SIZE);

    // Write full blocks
    for (size_t i = 0; i < fullBlocks; ++i) {
        outFile.write(reinterpret_cast<const char*>(data + (i * BLOCK_SIZE / sizeof(T))), BLOCK_SIZE);
    }

    // Write the last block with padding if necessary
    if (remainingBytes > 0) {
        char lastBlock[BLOCK_SIZE] = {0}; // Initialize last block with zeros
        memcpy(lastBlock, reinterpret_cast<const char*>(data + (fullBlocks * BLOCK_SIZE / sizeof(T))), remainingBytes);
        outFile.write(lastBlock, BLOCK_SIZE);
    }

    outFile.close();
}

template<typename T>
int readFile(const string& filename, size_t elementCount, T* data, size_t startBlock = 0) {
    ifstream inFile(filename, ios::binary);
    if (!inFile) {
        return -1; // Failed to open file
    }

    // Seek to the starting block position
    inFile.seekg(startBlock * BLOCK_SIZE);

    size_t totalBytes = elementCount * sizeof(T);
    size_t fullBlocks = totalBytes / BLOCK_SIZE;
    size_t remainingBytes = totalBytes % BLOCK_SIZE;

    // Read full blocks
    for (size_t i = 0; i < fullBlocks; ++i) {
        inFile.read(reinterpret_cast<char*>(data + (i * BLOCK_SIZE / sizeof(T))), BLOCK_SIZE);
        if (inFile.fail()) {
            inFile.close();
            return -2; // Read error or EOF
        }
    }

    // Read remaining bytes (if any)
    if (remainingBytes > 0) {
        inFile.read(reinterpret_cast<char*>(data + (fullBlocks * BLOCK_SIZE / sizeof(T))), remainingBytes);
        if (inFile.fail()) {
            inFile.close();
            return -2; // Read error or EOF
        }
    }

    inFile.close();
    return 0;
}

int main() {
    // Example usage with string data at different blocks
    const char* strData1 = "Hello, World!";
    const char* strData2 = "Second block data";
    size_t strLen1 = strlen(strData1) + 1;
    size_t strLen2 = strlen(strData2) + 1;

    // Write first string at block 0
    writeDataInBlocks(strData1, strLen1, "multi_block.bin", 0);
    // Write second string at block 1
    writeDataInBlocks(strData2, strLen2, "multi_block.bin", 1);

    // Read both strings back
    char readStrData1[BLOCK_SIZE];
    char readStrData2[BLOCK_SIZE];
    readFile<char>("multi_block.bin", strLen1, readStrData1, 0);  // Read from block 0
    readFile<char>("multi_block.bin", strLen2, readStrData2, 1);  // Read from block 1

    cout << "Read from block 0: " << readStrData1 << endl;
    cout << "Read from block 1: " << readStrData2 << endl;

    // 创建学生数据示例
    Student students[] = {
        Student("Alicffffffffffffdfffffffffffe", 20, 95.5f, 'A'),
        Student("Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbob", 22, 88.0f, 'B'),
        Student("Chbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbarlie", 21, 92.5f, 'A')
    };
    size_t studentCount = sizeof(students) / sizeof(Student);
    cout<<sizeof(students)<<endl;
    // 将整个学生数组写入文件,自动分块
    writeDataInBlocks(students, studentCount, "students.bin");

    // 读取学生数据
    Student readStudents[3];
    readFile<Student>("students.bin", studentCount, readStudents);

    // 打印读取的学生信息
    for (size_t i = 0; i < studentCount; ++i) {
        cout << "\nStudent " << i + 1 << " info:" << endl;
        cout << "Name: " << readStudents[i].name << endl;
        cout << "Age: " << readStudents[i].age << endl;
        cout << "Score: " << readStudents[i].score << endl;
        cout << "Grade: " << readStudents[i].grade << endl;
    }

    return 0;
}

普通的二进制写入数据

#include <fstream>
#include <iostream>
#include <string>

// 函数声明
void WriteBinaryToFile(const std::string& filename, int number, const std::string& text);
void ReadBinaryFromFile(const std::string& filename, int& number, std::string& text);

int main() {
    // 要写入的数据
    int number = 12345;
    std::string text = "Hello, World!";
    
    // 写入二进制数据到文件
    WriteBinaryToFile("data.bin", number, text);
    
    // 读取二进制数据并转换
    int readNumber;
    std::string readText;
    ReadBinaryFromFile("data.bin", readNumber, readText);
    
    // 输出读取的数据
    std::cout << "Number: " << readNumber << std::endl;
    std::cout << "Text: " << readText << std::endl;

    return 0;
}

// 将数据以二进制形式写入文件
void WriteBinaryToFile(const std::string& filename, int number, const std::string& text) {
    //binary,写入方式
	std::ofstream outFile(filename, std::ios::binary);
    if (!outFile) {
        std::cerr << "Error opening file for writing" << std::endl;
        return;
    }
    outFile.write(reinterpret_cast<const char*>(&number), sizeof(number));
    outFile.write(text.c_str(), text.size() + 1);
    outFile.close();
}

// 从文件读取二进制数据并转换
void ReadBinaryFromFile(const std::string& filename, int& number, std::string& text) {
    std::ifstream inFile(filename, std::ios::binary);
    if (!inFile) {
        std::cerr << "Error opening file for reading" << std::endl;
        return;
    }
    inFile.read(reinterpret_cast<char*>(&number), sizeof(number));
    char c;
    text.clear();
    while (inFile.get(c)) {
        text += c;
    }
    inFile.close();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值