第15章 文件与IO操作:QFile、QTextStream、QDataStream、QDir、QFileInfo、QFileSystemWatcher
文件操作,说白了就是程序跟硬盘打交道的那点事。我做了这么多年Qt开发,可以负责任地告诉你:文件IO这块要是没搞明白,后面做项目肯定要踩坑。今天咱们就把Qt里这几个文件相关的类一次性说清楚。
本章核心知识点一览:
- QFile:最基础的文件读写操作
- QTextStream:文本流的高效处理
- QDataStream:二进制数据的序列化
- QDir:目录的遍历与管理
- QFileInfo:文件属性的查询
- QFileSystemWatcher:文件系统的实时监控
15.1 QFile:最基础的文件操作
QFile 是 Qt 文件操作的基石。我个人习惯把它当作「文件句柄」来理解——你打开它、读写它、最后关闭它。就这么简单。
// 最基本的文件读取
QFile file("data.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "文件打开失败:" << file.errorString();
return;
}
QByteArray content = file.readAll();
qDebug() << "文件内容:" << content;
file.close();
注意:千万别忘了调用 close()。我曾经在项目里见过一个同事,打开文件后忘记关闭,结果程序跑了一整天,文件句柄泄露导致系统崩溃。嗯,血的教训。
QFile 支持多种打开模式,我整理了一张表,你直接收藏就行:
| 模式 | 说明 | 常用场景 |
|---|---|---|
| ReadOnly | 只读模式 | 读取配置文件 |
| WriteOnly | 只写模式(会清空原文件) | 写入日志 |
| ReadWrite | 读写模式 | 修改现有文件 |
| Append | 追加模式(写入到文件末尾) | 追加日志记录 |
| Truncate | 截断模式(清空文件) | 重置文件内容 |
15.2 QTextStream:文本流的高效处理
你想想看,如果每次读写文件都要手动处理编码、换行符,那得多麻烦?QTextStream 就是来解决这个问题的。它自动处理了字符编码和格式化,用起来特别顺手。
// 使用 QTextStream 写入文件
QFile file("config.ini");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream stream(&file);
stream << "[Settings]" << Qt::endl;
stream << "width=1024" << Qt::endl;
stream << "height=768" << Qt::endl;
file.close();
}
// 读取文件
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
qDebug() << "读取到:" << line;
}
file.close();
}
小技巧:QTextStream 默认使用 UTF-8 编码。如果你需要处理 GBK 或其他编码,记得调用 setEncoding() 方法。我在做中文项目时经常遇到编码问题,这个坑我已经替你们踩过了。
15.3 QDataStream:二进制数据的序列化
说白了,QDataStream 就是用来处理二进制数据的。它最大的好处是——你不需要关心字节序、数据对齐这些底层细节。我当年做网络协议解析时,就是靠它省了不少事。
// 写入二进制数据
QFile file("data.bin");
if (file.open(QIODevice::WriteOnly)) {
QDataStream out(&file);
out << QString("Hello Qt");
out << qint32(42);
out << double(3.14159);
file.close();
}
// 读取二进制数据
if (file.open(QIODevice::ReadOnly)) {
QDataStream in(&file);
QString str;
qint32 num;
double pi;
in >> str >> num >> pi;
qDebug() << str << num << pi;
file.close();
}
注意:QDataStream 的版本很重要。不同 Qt 版本之间的序列化格式可能不兼容。我建议你显式设置版本号:stream.setVersion(QDataStream::Qt_5_15); 这样跨版本读取时就不会出问题。
15.4 QDir:目录操作的艺术
QDir 是 Qt 里处理目录的利器。创建目录、遍历文件、获取路径信息,它都能搞定。我个人特别喜欢它的过滤器机制,可以精确控制要获取哪些文件。
// 创建目录
QDir dir;
if (!dir.exists("backup")) {
dir.mkdir("backup");
}
// 遍历目录下的所有 .txt 文件
QDir targetDir("documents");
QStringList filters;
filters << "*.txt" << "*.md";
targetDir.setNameFilters(filters);
QFileInfoList fileList = targetDir.entryInfoList();
foreach (const QFileInfo &info, fileList) {
qDebug() << "文件名:" << info.fileName();
qDebug() << "大小:" << info.size() << "字节";
}
经验之谈:跨平台开发时,路径分隔符是个大坑。Windows 用反斜杠,Linux 用正斜杠。QDir 提供了 toNativeSeparators() 方法,可以自动转换。我建议你永远用正斜杠写路径,让 Qt 帮你处理。
15.5 QFileInfo:文件信息的百科全书
QFileInfo 就像文件的「身份证」,能告诉你这个文件的所有信息:创建时间、修改时间、大小、权限、后缀名……应有尽有。
QFileInfo info("document.pdf");
if (info.exists()) {
qDebug() << "绝对路径:" << info.absoluteFilePath();
qDebug() << "文件名:" << info.fileName();
qDebug() << "后缀:" << info.suffix();
qDebug() << "大小:" << info.size() << "字节";
qDebug() << "最后修改:" << info.lastModified().toString("yyyy-MM-dd hh:mm:ss");
qDebug() << "是否可读:" << info.isReadable();
qDebug() << "是否可写:" << info.isWritable();
}
15.6 QFileSystemWatcher:实时监控文件变化
这个类很有意思。它能让你的程序「感知」到文件或目录的变化。比如配置文件被修改了,程序可以自动重新加载。我曾经用它做过一个日志监控工具,效果非常好。
// 创建文件监控器
QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
// 添加要监控的文件和目录
watcher->addPath("config.ini");
watcher->addPath("logs/");
// 连接信号槽
connect(watcher, &QFileSystemWatcher::fileChanged, this, [](const QString &path) {
qDebug() << "文件发生变化:" << path;
// 在这里重新加载配置
});
connect(watcher, &QFileSystemWatcher::directoryChanged, this, [](const QString &path) {
qDebug() << "目录发生变化:" << path;
// 在这里刷新文件列表
});
注意:QFileSystemWatcher 在某些平台上可能不太稳定。比如在 Linux 上,如果监控的文件被删除再重建,监控可能会失效。我建议你定期检查监控状态,必要时重新添加监控路径。
15.7 综合示例:一个简单的日志系统
光说不练假把式。咱们来写一个完整的日志系统,把今天学的知识点串起来。
class Logger : public QObject {
Q_OBJECT
public:
explicit Logger(const QString &logDir, QObject *parent = nullptr)
: QObject(parent), m_logDir(logDir) {
// 确保日志目录存在
QDir dir;
dir.mkpath(m_logDir);
// 设置日志文件路径
m_logFile.setFileName(m_logDir + "/app.log");
// 监控日志目录
m_watcher.addPath(m_logDir);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged,
this, &Logger::onDirectoryChanged);
}
void writeLog(const QString &message) {
if (!m_logFile.open(QIODevice::Append | QIODevice::Text)) {
qWarning() << "无法打开日志文件";
return;
}
QTextStream stream(&m_logFile);
QString timestamp = QDateTime::currentDateTime()
.toString("yyyy-MM-dd hh:mm:ss");
stream << "[" << timestamp << "] " << message << Qt::endl;
m_logFile.close();
}
void backupLog() {
QFileInfo info(m_logFile);
if (info.size() > 1024 * 1024) { // 超过1MB就备份
QString backupName = m_logDir + "/app_" +
QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + ".log";
QFile::copy(m_logFile.fileName(), backupName);
m_logFile.remove();
}
}
private slots:
void onDirectoryChanged(const QString &path) {
qDebug() << "日志目录发生变化:" << path;
backupLog();
}
private:
QString m_logDir;
QFile m_logFile;
QFileSystemWatcher m_watcher;
};
这个日志系统用到了 QDir 创建目录、QFile 追加写入、QTextStream 格式化输出、QFileInfo 检查文件大小、QFileSystemWatcher 监控目录变化。你看,这些类配合起来,能做出很实用的功能。
总结一下:Qt 的文件IO操作其实不复杂。记住几个要点:
- 文本文件用 QTextStream,省心省力
- 二进制数据用 QDataStream,自动处理字节序
- 目录操作交给 QDir,跨平台无压力
- 文件信息查询用 QFileInfo,一查一个准
- 需要监控文件变化?QFileSystemWatcher 是你的好帮手
好了,这一章的内容就到这里。记住,文件操作是每个程序员的必修课,多写多练,自然就熟练了。
公众号:蓝海资料掘金营,微信deep3321