如何在C++中更快读取百万行以上的CSV文件?
百万行CSV文件读取优化方案
针对百万行CSV读取的性能瓶颈,结合你的现有代码,可从以下几个方向针对性优化:
1. 预分配Vector内存
std::vector默认动态扩容会频繁触发内存重分配与元素拷贝,对百万级数据开销极大。提前预估数据量并调用reserve预留内存,彻底避免扩容开销:
std::vector<OrderBookEntry> entries; // 按实际数据量预留(比如预估120万行,留冗余空间) entries.reserve(1200000); std::ifstream csvFile{ csvFilename };
2. 加速文件IO操作
关闭标准流同步
禁用std::ifstream与C标准IO的同步机制,减少跨流调度开销:
std::ifstream csvFile{ csvFilename }; csvFile.sync_with_stdio(false); csvFile.tie(nullptr);
一次性读取整个文件到内存
逐行getline会频繁触发磁盘IO,一次性读取整个文件到内存后再处理,能大幅减少IO交互次数:
std::ifstream csvFile{ csvFilename, std::ios::ate }; if (!csvFile.is_open()) { /* 处理文件打开失败逻辑 */ } // 获取文件大小并分配内存 std::streampos fileSize = csvFile.tellg(); std::string fileContent(fileSize, '\0'); csvFile.seekg(0); csvFile.read(&fileContent[0], fileSize); // 在内存中分割行处理 size_t pos = 0; size_t nextPos = fileContent.find('\n'); while (nextPos != std::string::npos) { std::string line = fileContent.substr(pos, nextPos - pos); // 处理当前行逻辑 pos = nextPos + 1; nextPos = fileContent.find('\n', pos); } // 处理最后一行(若文件末尾无换行) if (pos < fileContent.size()) { std::string line = fileContent.substr(pos); // 处理最后一行逻辑 }
3. 优化字符串分割与对象构造
用string_view替代字符串拷贝
如果你的tokenise函数会频繁创建新字符串,改用std::string_view直接引用原字符串内存,避免不必要的拷贝:
// 手动分割逗号分隔字符串,返回string_view列表(需C++17及以上) std::vector<std::string_view> tokenise_view(const std::string& line, char delimiter) { std::vector<std::string_view> tokens; size_t start = 0; size_t end = line.find(delimiter); while (end != std::string::npos) { tokens.emplace_back(line.data() + start, end - start); start = end + 1; end = line.find(delimiter, start); } tokens.emplace_back(line.data() + start, line.size() - start); return tokens; }
用emplace_back替代push_back
push_back会先构造临时对象再拷贝到vector,emplace_back直接在vector的内存空间中构造对象,减少一次拷贝构造:
// 原代码:entries.push_back(obe); // 修改为直接在vector内构造对象 entries.emplace_back(price, amount, timestamp, product, orderType, username); // 需调整stringsToOBE逻辑,直接返回构造参数或转发到emplace_back
4. 内存映射文件(进阶优化)
利用操作系统的内存映射(mmap)将文件直接映射到进程地址空间,跳过内核态到用户态的数据拷贝,IO性能最优:
#include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> // 打开文件 int fd = open(csvFilename.c_str(), O_RDONLY); if (fd == -1) { /* 处理打开错误 */ } // 获取文件大小 struct stat sb; if (fstat(fd, &sb) == -1) { /* 处理错误 */ } // 映射文件到内存 char* fileData = static_cast<char*>(mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0)); if (fileData == MAP_FAILED) { /* 处理映射错误 */ } // 直接操作fileData分割行与字段 // ... // 使用完毕后释放映射与文件句柄 munmap(fileData, sb.st_size); close(fd);
5. 减少错误处理的IO开销
当前代码每次捕获异常都会调用cout输出,频繁IO会拖慢速度。可将错误信息缓存到字符串中,最后统一输出:
std::string errorLog; try { // 行处理逻辑 } catch (const std::exception& e) { errorLog += "CSVReader::readCSV bad data: " + std::string(e.what()) + "\n"; } // 循环结束后统一输出错误 if (!errorLog.empty()) { std::cout << errorLog; }
内容的提问来源于stack exchange,提问作者Tan Jun Rong
相关产品推荐
相关产品推荐

