2、QFile高级操作:读写文本文件、二进制文件、临时文件与文件锁
文件操作是Qt开发里躲不开的活。说实话,很多人用了好几年QFile,还只停留在open、read、write三板斧。遇到大文件、二进制结构、多进程冲突,就开始头疼了。
这一章,我把QFile的四个核心场景拆开讲:文本读写、二进制操作、临时文件管理、文件锁机制。每个点我都会带上实际项目里的坑和习惯用法。
2.1 文本文件读写:别再用readAll()了
先说说最常见的文本文件。很多人图省事,上来就readAll()。小文件没问题,但一个几百兆的日志文件,你试试?内存直接爆炸。
readAll()逐行解析,结果程序跑三天就OOM崩溃。后来改成流式读取,内存占用从500MB降到不到2MB。
正确的做法是逐行读取,或者按块读取。Qt提供了QTextStream,配合readLine(),这才是正道。
// 逐行读取大文本文件
QFile file("server.log");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "打开文件失败:" << file.errorString();
return;
}
QTextStream in(&file);
in.setCodec("UTF-8"); // 我习惯显式指定编码,避免跨平台乱码
while (!in.atEnd()) {
QString line = in.readLine();
// 处理每一行
processLine(line);
}
file.close();
写入文本时,同样用QTextStream。注意一点:频繁调用flush()会影响性能,但关键数据写入后最好手动刷一次。
QFile file("output.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << "时间: " << QDateTime::currentDateTime().toString(Qt::ISODate) << "\n";
out << "状态: " << statusCode << "\n";
out.flush(); // 关键数据写入后刷盘
file.close();
}
setCodec("UTF-8"),省得跨平台时出幺蛾子。
2.2 二进制文件读写:结构体直接读写
二进制文件比文本文件效率高得多,尤其适合存储结构化数据。比如传感器数据、图像像素、网络包缓存等。
Qt里读写二进制文件,核心是QDataStream。它能把C++的基本类型和Qt的容器类型直接序列化到文件里。
// 写入二进制数据
QFile file("sensor_data.bin");
if (file.open(QIODevice::WriteOnly)) {
QDataStream out(&file);
out.setByteOrder(QDataStream::LittleEndian); // 我习惯用小端序,兼容x86
// 写入结构体
quint32 timestamp = 1628000000;
double temperature = 36.5;
quint16 humidity = 75;
out << timestamp << temperature << humidity;
file.close();
}
// 读取二进制数据
if (file.open(QIODevice::ReadOnly)) {
QDataStream in(&file);
in.setByteOrder(QDataStream::LittleEndian);
quint32 timestamp;
double temperature;
quint16 humidity;
in >> timestamp >> temperature >> humidity;
file.close();
}
你想想看,如果用文本方式存这些数据,还得做字符串转换,效率差一个数量级。二进制读写直接内存拷贝,快得很。
QDataStream的版本号很重要。不同Qt版本默认版本号不同,跨版本读写会出问题。我建议显式设置版本号:out.setVersion(QDataStream::Qt_5_15);
2.3 临时文件:安全创建,自动清理
临时文件在什么场景下用?我举几个例子:程序升级时下载临时包、编辑器自动保存的备份、大数据排序时的中间缓存。
Qt提供了QTemporaryFile,比手动创建临时文件安全得多。它自动生成唯一文件名,而且对象销毁时自动删除文件。
// 创建临时文件
QTemporaryFile tempFile;
tempFile.setFileTemplate(QDir::tempPath() + "/myapp_XXXXXX.txt");
if (tempFile.open()) {
qDebug() << "临时文件路径:" << tempFile.fileName();
// 写入临时数据
QTextStream out(&tempFile);
out << "这是临时数据";
// 文件路径可以传给其他模块使用
QString filePath = tempFile.fileName();
// 临时文件会在对象析构时自动删除
// 如果不想自动删除,调用 tempFile.setAutoRemove(false);
}
open()一直返回false。后来发现是文件系统编码问题。老老实实用英文路径,稳得很。
临时文件的典型应用场景:
| 场景 | 说明 | 注意事项 |
|---|---|---|
| 软件升级 | 下载更新包到临时目录 | 下载完成后移动到安装目录 |
| 编辑器备份 | 定时保存编辑中的文档 | 崩溃后恢复时保留临时文件 |
| 大数据排序 | 外部排序的中间缓存 | 注意磁盘空间,用完即删 |
| 网络缓存 | 下载大文件的分片缓存 | 断点续传时不能自动删除 |
2.4 文件锁:多进程安全的关键
多进程同时读写同一个文件,数据错乱是必然的。我见过最惨的一次,两个进程同时写配置文件,结果文件内容变成两段数据拼在一起,直接解析失败。
Qt提供了QLockFile,专门解决这个问题。它通过文件系统级别的锁文件来实现互斥。
// 使用文件锁保护共享资源
QLockFile lockFile(QDir::tempPath() + "/config.lock");
lockFile.setStaleLockTime(30000); // 30秒后锁自动失效
if (lockFile.tryLock(1000)) { // 等待1秒
// 成功获取锁,可以安全读写共享文件
QFile configFile("shared_config.ini");
if (configFile.open(QIODevice::ReadWrite)) {
// 读写操作...
configFile.close();
}
lockFile.unlock(); // 释放锁
} else {
qWarning() << "无法获取文件锁,其他进程正在使用";
}
setStaleLockTime这个参数很关键,防止进程崩溃后锁永远不释放。
文件锁的几个要点:
- 锁文件是独立的:锁文件本身不包含数据,只是一个标记
- 自动清理:进程崩溃后,锁文件可能残留,
staleLockTime可以兜底 - 跨平台:Windows和Linux下都能用,底层实现不同但接口一致
- 不要用于线程同步:
QLockFile是进程间锁,线程间用QMutex
2.5 知识体系总览
下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引,遇到具体问题时直接定位到对应模块。
嗯,到这里,QFile的四个高级操作就讲完了。文本读写别再用readAll(),二进制数据用QDataStream,临时文件交给QTemporaryFile,多进程安全靠QLockFile。这些套路我在项目里反复用,基本没出过问题。