2. QFile基础读写:打开、读取、写入文本文件
好,咱们正式开始动手操作文件了。
QFile 是 Qt 里最基础的文件操作类。说白了,它就是一把钥匙,帮你打开硬盘上的文件,然后读点东西进去,或者写点东西出来。我刚开始学 Qt 时,第一个接触的文件类就是它,简单直接,没什么花哨的包装。
2.1 打开文件:OpenMode 标志位
打开文件,你得告诉 QFile 你想干嘛。是只读?只写?还是读写?这就是 OpenMode 的作用。
我个人习惯把 OpenMode 理解成「权限清单」。你给 QFile 一个标志位组合,它就知道该怎么操作这个文件。
| 标志位 | 含义 | 常用场景 |
|---|---|---|
QIODevice::ReadOnly |
只读模式 | 读取配置文件、日志分析 |
QIODevice::WriteOnly |
只写模式 | 写入新文件、覆盖旧内容 |
QIODevice::ReadWrite |
读写模式 | 需要同时读写的场景 |
QIODevice::Append |
追加模式 | 日志文件、记录追加 |
QIODevice::Truncate |
截断模式 | 清空文件再写入 |
QIODevice::Text |
文本模式 | 处理换行符转换(Windows 下 \r\n 转 \n) |
核心要点:标志位可以用 | 组合使用。比如 QIODevice::WriteOnly | QIODevice::Text 表示以文本模式只写。
嗯,这里要注意:Truncate 和 WriteOnly 一起用时,文件内容会被清空。我曾经在项目里犯过这个错——想追加日志,结果忘了加 Append,直接把之前的日志全抹掉了。那叫一个惨。
2.2 读取文本文件
读取文件,我常用的方法有三种。你根据场景选就行。
方法一:一次性读取全部内容
QFile file("example.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "文件打开失败:" << file.errorString();
return;
}
QString content = file.readAll();
file.close();
// 现在 content 里就是整个文件的内容
qDebug() << content;
这个方法适合小文件。比如配置文件、简单的数据文件。我一般用它来读几百 KB 以内的文本,再大就不建议了,容易撑爆内存。
方法二:按行读取
QFile file("log.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
// 处理每一行
processLine(line);
}
file.close();
按行读取是我最常用的方式。你想想看,日志文件、CSV 文件,哪个不是一行一条记录?用 readLine() 逐行处理,内存占用小,也方便做行号追踪。
小技巧:用 QTextStream 包装 QFile,可以自动处理编码问题。默认是 UTF-8,你也可以用 setEncoding() 指定其他编码。
方法三:按指定大小读取
QFile file("large_file.txt");
if (!file.open(QIODevice::ReadOnly)) {
return;
}
char buffer[1024];
qint64 bytesRead = 0;
while ((bytesRead = file.read(buffer, sizeof(buffer))) > 0) {
// 处理 buffer 中的 bytesRead 个字节
processBuffer(buffer, bytesRead);
}
file.close();
这个方法我很少用,除非处理超大文件。比如几个 GB 的文本,你不可能一次性读到内存里。按块读取,边读边处理,才是正道。
2.3 写入文本文件
写入比读取简单。无非就是打开文件,写数据,关闭文件。但有几个坑,我得提醒你。
基本写入示例
QFile file("output.txt");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "无法写入文件";
return;
}
QTextStream out(&file);
out << "第一行内容" << "\n";
out << "第二行内容" << "\n";
out << "当前数值: " << 42 << "\n";
file.close();
注意看,我用的是 QTextStream 的 << 运算符。这玩意儿跟 cout 很像,写起来顺手。而且它会自动处理编码转换,省心。
避坑指南:我曾经在写入时忘记加 QIODevice::Text 标志位。结果在 Windows 上,换行符只写了 \n,没有 \r\n。用记事本打开,所有内容都挤在一行。加上 Text 标志位后,Qt 会自动帮你做换行符转换。
追加写入
QFile file("log.txt");
if (!file.open(QIODevice::Append | QIODevice::Text)) {
return;
}
QTextStream out(&file);
out << "[" << QDateTime::currentDateTime().toString() << "] 新日志条目\n";
file.close();
追加模式不会清空原有内容,新数据直接写在文件末尾。做日志系统时,这个模式是标配。
2.4 完整示例:读写配置文件
来,看一个实际点的例子。假设我们要读写一个简单的配置文件,格式是 key=value。
// 写入配置
void writeConfig(const QString &filePath, const QMap<QString, QString> &config) {
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "写入配置失败";
return;
}
QTextStream out(&file);
for (auto it = config.begin(); it != config.end(); ++it) {
out << it.key() << "=" << it.value() << "\n";
}
file.close();
}
// 读取配置
QMap<QString, QString> readConfig(const QString &filePath) {
QMap<QString, QString> config;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return config;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#')) {
continue; // 跳过空行和注释
}
int eqPos = line.indexOf('=');
if (eqPos > 0) {
QString key = line.left(eqPos).trimmed();
QString value = line.mid(eqPos + 1).trimmed();
config[key] = value;
}
}
file.close();
return config;
}
这个例子我实际项目里用过很多次。注意我跳过了空行和以 # 开头的注释行。写配置文件时,加注释是个好习惯,方便别人(也方便未来的自己)理解。
2.5 知识体系图
下面这张图,把 QFile 基础读写的核心流程串起来了。你照着这个思路走,基本不会出错。
这张图把整个流程串起来了。你注意看,打开文件后的分支判断是关键。我见过太多新手直接假设文件一定能打开,结果程序崩溃了还不知道为什么。
总结一下:QFile 基础读写就三步——创建、打开、操作、关闭。打开时选对标志位,读写时选对方法,关闭时别忘了调用 close()。就这么简单。
嗯,这一节的内容就到这里。代码不多,但都是实打实能用的。你回去把示例跑一遍,感受一下 QFile 的脾气,后面咱们再聊更高级的用法。