第15章 文件与IO操作:QFile、QTextStream、QDataStream、QDir、QFileInfo、QFileSystemWatcher

文件操作,说白了就是程序跟硬盘打交道的那点事。我做了这么多年Qt开发,可以负责任地告诉你:文件IO这块要是没搞明白,后面做项目肯定要踩坑。今天咱们就把Qt里这几个文件相关的类一次性说清楚。

本章核心知识点一览:

  • QFile:最基础的文件读写操作
  • QTextStream:文本流的高效处理
  • QDataStream:二进制数据的序列化
  • QDir:目录的遍历与管理
  • QFileInfo:文件属性的查询
  • QFileSystemWatcher:文件系统的实时监控
Qt 文件与IO操作 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