You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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个,属于不必要的资源浪费。

修复方案

  1. 以二进制模式读写文件:确保文件操作基于原始字节流,避免换行符转换带来的位置偏差。
  2. 重构循环终止逻辑:不再依赖tellg(),改为通过计算剩余待读取字节数结合流状态判断循环是否结束。
  3. 清理冗余代码:仅创建实际需要的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 00:54:59