在C++开发场景中,读取GB级别的大型文本文件时,如果程序没有任何进度反馈,用户很难判断程序是否在正常运行,动态更新控制台滚动进度条就能很好地解决这个问题。实现这个功能的核心是先获取文件的总大小,再在读取过程中统计已读取的字节数,计算出当前进度后实时刷新控制台显示。
核心实现思路
整个功能的实现可以分为三个步骤:
- 打开目标文件,获取文件的总字节数,作为进度计算的分母
- 循环读取文件内容,每次读取后累加已读取的字节数,计算当前进度百分比
- 根据进度百分比,在控制台中绘制对应长度的进度条,并且覆盖之前的显示内容,实现动态更新效果
关键技术点说明
获取文件总大小
可以通过fstream将文件指针移动到末尾,再通过tellg方法获取当前位置,也就是文件的总大小。注意操作完成后要将文件指针移回开头,方便后续读取内容。
控制台动态刷新
控制台中要实现进度条动态更新,不需要每次都换行输出,只需要使用回车符r,就可以将光标移动到当前行的开头,后续的输出会覆盖之前的内容,实现进度条的滚动效果。
进度条绘制逻辑
通常进度条由固定的总宽度和已填充的宽度组成,比如总宽度为50个字符,当前进度是30%,那么已填充的宽度就是15个字符,剩余部分用空格或者其他符号填充即可。
完整代码示例
以下是完整的可运行代码,实现了读取大型文本文件时动态更新控制台滚动进度条的功能:
#include <iostream>
#include <fstream>
#include <string>
#include <chrono>
#include <thread>
using namespace std;
// 获取文件总大小,单位字节
long long getFileSize(const string& filePath) {
ifstream file(filePath, ios::binary | ios::ate);
if (!file.is_open()) {
return -1;
}
long long size = file.tellg();
file.close();
return size;
}
// 绘制进度条,totalSize是文件总大小,readSize是已读取大小,barWidth是进度条总宽度
void drawProgressBar(long long totalSize, long long readSize, int barWidth = 50) {
if (totalSize <= 0) {
return;
}
// 计算进度百分比,保留两位小数
double progress = static_cast<double>(readSize) / totalSize;
if (progress > 1.0) {
progress = 1.0;
}
int pos = static_cast<int>(barWidth * progress);
// 输出回车符,回到行首覆盖之前的内容
cout << "r[";
// 填充已完成部分
for (int i = 0; i < pos; ++i) {
cout << "=";
}
// 填充未完成部分
for (int i = pos; i < barWidth; ++i) {
cout << " ";
}
// 输出进度百分比
cout << "] " << int(progress * 100.0) << "%";
// 刷新输出缓冲区,确保内容立即显示
cout.flush();
}
int main() {
string filePath = "large_text_file.txt"; // 替换为你的大型文本文件路径
long long totalSize = getFileSize(filePath);
if (totalSize == -1) {
cout << "无法打开文件: " << filePath << endl;
return 1;
}
if (totalSize == 0) {
cout << "文件为空" << endl;
return 0;
}
ifstream inFile(filePath, ios::binary);
if (!inFile.is_open()) {
cout << "无法打开文件: " << filePath << endl;
return 1;
}
const int bufferSize = 4096; // 每次读取4KB
char buffer[bufferSize];
long long readSize = 0;
// 初始绘制进度条
drawProgressBar(totalSize, readSize);
// 循环读取文件
while (inFile.read(buffer, bufferSize)) {
readSize += inFile.gcount();
drawProgressBar(totalSize, readSize);
// 模拟处理读取到的数据,实际场景中这里可以替换为你的业务逻辑
this_thread::sleep_for(chrono::milliseconds(10));
}
// 处理最后一次读取可能不足bufferSize的情况
if (inFile.gcount() > 0) {
readSize += inFile.gcount();
drawProgressBar(totalSize, readSize);
}
inFile.close();
// 读取完成后换行,避免后续输出和进度条在同一行
cout << endl << "文件读取完成" << endl;
return 0;
}
代码使用说明
使用上述代码时,只需要将filePath变量替换为你要读取的大型文本文件的实际路径即可。代码中使用4KB的缓冲区循环读取文件,每次读取完成后更新已读取字节数并重新绘制进度条,同时加入了10毫秒的延迟模拟数据处理过程,实际使用时可以根据需求调整缓冲区大小和延迟时间。
如果需要适配Windows系统的控制台,可能需要额外处理控制台的编码问题,避免中文路径或者中文内容显示乱码,也可以通过Windows的API调整控制台缓冲区大小,让进度条显示更完整。