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 基础读写流程 创建 QFile 对象 open(OpenMode 标志位) 失败 处理错误,返回 成功 执行读写操作 close() 关闭文件 常用标志位 ReadOnly WriteOnly ReadWrite Append Truncate Text 读取方式 readAll() readLine() read(buffer, size) QTextStream

这张图把整个流程串起来了。你注意看,打开文件后的分支判断是关键。我见过太多新手直接假设文件一定能打开,结果程序崩溃了还不知道为什么。

总结一下:QFile 基础读写就三步——创建、打开、操作、关闭。打开时选对标志位,读写时选对方法,关闭时别忘了调用 close()。就这么简单。

嗯,这一节的内容就到这里。代码不多,但都是实打实能用的。你回去把示例跑一遍,感受一下 QFile 的脾气,后面咱们再聊更高级的用法。