使用seekg()与tellg()分割大文件时,tellg()返回-1致循环卡死
解决C++分割大文件时tellg()返回-1导致循环卡住的问题
问题根源
- 未以二进制模式打开文件:你打开输入文件时未指定
std::ios::binary,文本模式下系统会自动转换换行符(如Windows下\r\n转\n),导致tellg()返回的位置与实际文件字节数不匹配,且读取到文件末尾时流状态异常,tellg()返回-1。 - 循环条件依赖错误的
tellg()值:当read()读取到文件末尾(或最后一次未读满缓冲区)时,流的failbit会被触发,此时tellg()返回-1,而-1 < endPos永远为真,直接导致循环无限执行。 - 冗余资源创建:代码中创建了16个输出文件,但实际仅使用前4个,属于不必要的资源浪费。
修复方案
- 以二进制模式读写文件:确保文件操作基于原始字节流,避免换行符转换带来的位置偏差。
- 重构循环终止逻辑:不再依赖
tellg(),改为通过计算剩余待读取字节数结合流状态判断循环是否结束。 - 清理冗余代码:仅创建实际需要的4个输出文件,减少资源占用。
修改后的代码
#include <iostream> #include <fstream> #include <sstream> #include <string> #include <chrono> #include <cstdlib> #include <vector> #include <filesystem> namespace fs = std::filesystem; const int BUFFER_SIZE = 4096; class FileWriter { private: std::ofstream m_OutFile; char m_CharBuffer[BUFFER_SIZE]; public: FileWriter(const std::string& filename) { // 二进制模式打开输出文件 m_OutFile.open(filename.c_str(), std::ios::out | std::ios::binary); m_OutFile.rdbuf()->pubsetbuf(m_CharBuffer, BUFFER_SIZE); } ~FileWriter() { if (m_OutFile.is_open()) { m_OutFile.close(); } } void WriteToFile(std::ostringstream& oStr) { oStr << std::ends; m_OutFile << oStr.str() << std::endl; oStr.str(""); } }; bool directoryExists(const std::string& dirPath) { return fs::is_directory(dirPath); } bool createDirectory(const std::string& dirPath) { try { if (!directoryExists(dirPath)) { return fs::create_directories(dirPath); } } catch (const std::exception& e) { std::cerr << "Error creating directory: " << e.what() << std::endl; } return false; } void fileWriterV2(std::string inp, std::string out) { std::ifstream inputFile(inp, std::ios::binary); // 二进制模式打开输入文件 if (!inputFile) { std::cerr << "Failed to open input file." << std::endl; return; } // 处理输出目录结尾的斜杠 if (!out.empty() && out.back() != '/') { out += '/'; } // 获取文件大小 inputFile.seekg(0, std::ios::end); std::streampos fileSize = inputFile.tellg(); if (fileSize == -1) { std::cerr << "Failed to get file size." << std::endl; inputFile.close(); return; } std::streampos chunkSize = fileSize / 4; std::vector<std::ofstream> outputFiles; // 仅创建需要的4个输出文件 for (int i = 0; i < 4; ++i) { std::string outputDir = out + std::to_string(i + 1); if (!createDirectory(outputDir)) { std::cerr << "Directory creation failed for: " << outputDir << std::endl; // 清理已创建资源 for (auto& file : outputFiles) { if (file.is_open()) file.close(); } inputFile.close(); return; } std::string outputPath = outputDir + "/debug.db"; outputFiles.emplace_back(outputPath, std::ios::binary); if (!outputFiles.back().is_open()) { std::cerr << "Failed to open output file: " << outputPath << std::endl; // 清理资源 for (auto& file : outputFiles) { if (file.is_open()) file.close(); } inputFile.close(); return; } } char buffer[BUFFER_SIZE]; inputFile.seekg(0, std::ios::beg); std::streampos startPos = 0; for (int i = 0; i < 4; ++i) { std::streampos endPos = (i + 1) * chunkSize; if (i == 3) { endPos = fileSize; // 最后一块包含剩余所有字节 } inputFile.seekg(startPos, std::ios::beg); std::streampos bytesReadTotal = 0; const std::streampos bytesToRead = endPos - startPos; while (bytesReadTotal < bytesToRead) { // 计算本次读取的最大字节数,避免超出剩余量 std::streamsize readSize = static_cast<std::streamsize>( std::min(bytesToRead - bytesReadTotal, static_cast<std::streampos>(BUFFER_SIZE)) ); inputFile.read(buffer, readSize); std::streamsize actualRead = inputFile.gcount(); if (actualRead > 0) { outputFiles[i].write(buffer, actualRead); bytesReadTotal += actualRead; } // 非EOF的读取错误,终止循环 if (inputFile.fail() && !inputFile.eof()) { std::cerr << "Error reading input file at chunk " << i + 1 << std::endl; break; } // 到达文件末尾,退出循环 if (inputFile.eof()) break; } if (outputFiles[i].is_open()) outputFiles[i].close(); startPos = endPos; } inputFile.close(); // 写入分割信息示例 FileWriter writer(out + "split_summary.txt"); std::ostringstream oStr; oStr << "Total file size: " << fileSize << " bytes\n"; oStr << "Chunk size (except last): " << chunkSize << " bytes\n"; writer.WriteToFile(oStr); } int main(int argc, char** argv) { if (argc != 3) { std::cerr << "Usage: " << argv[0] << " <input_file> <output_directory>" << std::endl; return EXIT_FAILURE; } auto start = std::chrono::high_resolution_clock::now(); fileWriterV2(argv[1], argv[2]); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); std::cout << "File writing complete in " << duration << " ms" << std::endl; return EXIT_SUCCESS; }
关键修改说明
- 二进制模式:输入输出文件均添加
std::ios::binary,保证字节流的原始性,消除换行符转换带来的位置误差。 - 循环逻辑优化:通过
bytesReadTotal和bytesToRead控制循环,彻底摆脱对tellg()的依赖,避免-1导致的死循环。 - 错误处理增强:添加文件/目录创建失败时的资源清理逻辑,防止资源泄漏。
- 参数检查:主函数增加命令行参数数量校验,提示正确用法。
内容的提问来源于stack exchange,提问作者Tejas Sharma
相关产品推荐
相关产品推荐

