一、引言
在开发应用软件时,文件操作是不可或缺的一部分。为了高效地处理大文件或需要对特定位置进行随机访问的数据,我们通常会将数据分块处理。这篇文章将通过一个简单的例子向大家介绍如何使用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 实现分块写入与读取
接着,实现了两个模板函数writeDataInBlocks
和readFile
用于处理不同类型的对象分块写入和读取。这里的关键点在于根据数据大小计算出完整的块数量和剩余字节,然后使用文件流的seekp
和seekg
方法定位到正确的起始位置,再进行相应的读写操作。
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.bin
和students.bin
的文件,前者包含了两个字符串,后者则包含三个学生的信息。通过终端输出可以看到,我们成功地将信息写入文件,并准确无误地读取了回来。
四、总结
在C++中,使用char*
(或const char*
)而不是string
确实各有优缺点。
优点:
- 内存布局确定性:
char*
是连续的内存块,内存布局是可预测的string
的内部实现可能因编译器不同而不同,可能包含额外的成员(如长度、容量等)
- 二进制兼容性:
char*
更适合二进制I/O,因为它就是原始的字节序列string
可能包含额外的元数据,直接写入会导致问题
- 性能考虑:
char*
是最基本的内存表示,没有额外开销string
需要管理动态内存,有构造、析构等开销
- 跨平台/跨语言兼容性:
char*
是C风格的字符串表示,几乎所有编程语言都支持string
的实现在不同编译器间可能不兼容
缺点:
-
安全性较低:
- 需要手动管理内存
- 容易发生缓冲区溢出
- 需要手动处理字符串结束符’\0’
-
使用不便:
- 没有内置的字符串操作函数
- 需要手动计算长度
- 字符串拼接等操作更复杂
在代码中使用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操作,需要:
- 精确控制内存布局
- 确保数据的二进制表示是可预测的
- 避免写入string的额外元数据
建议:
- 如果是普通的字符串处理,建议使用
string
,更安全、更方便 - 如果是底层I/O、内存操作、跨语言接口等场景,使用
char*
更合适 - 在现代C++中,通常推荐使用
string_view
作为一个折中方案,它提供了类似char*
的性能优势,同时又有string
的安全性
writeDataInBlocks
和readFile
是按块写入的,并自动向下一块拓展,如果需要寻找没有使用的块并写入需要自己实现。例如模仿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();
}