27、Qt文件操作与XML/JSON:QFile、QDir、QFileInfo、XML解析(QXmlStreamReader)、JSON解析(QJsonDocument)
文件操作和序列化格式,是每个桌面应用都绕不开的坎。我做了这么多年Qt开发,几乎每个项目都要跟文件打交道——配置文件、日志文件、数据交换文件,样样都来。今天咱们就把Qt里文件操作和XML/JSON解析这块彻底讲透。
核心知识点速览:QFile读写文件、QDir遍历目录、QFileInfo获取文件属性、QXmlStreamReader解析XML、QJsonDocument处理JSON。这五个类,基本覆盖了日常开发中90%的文件处理场景。
QFile:最基础的文件读写
QFile是Qt里最直接的文件操作类。说白了,它就是帮你打开文件、读数据、写数据、关文件。用法跟C++标准库的fstream差不多,但跟Qt的信号槽机制配合得更好。
我个人习惯用QFile搭配QTextStream做文本文件读写,或者搭配QDataStream做二进制文件读写。举个例子,写一个简单的配置文件读取:
// 文本文件读取
QFile file("config.ini");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "文件打开失败:" << file.errorString();
return;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
// 处理每一行
qDebug() << line;
}
file.close();
小技巧:记得检查open()的返回值。我在项目中遇到过好几次因为路径不对导致文件打不开,结果程序静默失败,排查了半天才发现是文件不存在。所以,养成检查返回值的习惯很重要。
写文件也类似,把OpenMode换成WriteOnly就行。不过要注意,默认是覆盖写,如果想追加内容,加上QIODevice::Append标志。
QDir:目录操作与文件遍历
QDir用来操作目录——创建、删除、重命名、遍历文件列表。你想想看,一个文件管理器软件,核心功能就是靠QDir实现的。
我最常用的几个方法:
- entryList():获取目录下的文件和子目录列表
- exists():判断目录是否存在
- mkpath():创建多级目录
- removeRecursively():递归删除目录及其内容
// 遍历当前目录下所有.cpp文件
QDir dir(".");
QStringList filters;
filters << "*.cpp" << "*.h";
dir.setNameFilters(filters);
QFileInfoList fileList = dir.entryInfoList();
foreach (const QFileInfo &info, fileList) {
qDebug() << "文件名:" << info.fileName()
<< "大小:" << info.size()
<< "最后修改:" << info.lastModified();
}
注意:entryList()默认不排序。如果你需要按文件名或时间排序,记得调用setSorting()。我曾经因为没排序,结果文件列表顺序每次都不一样,导致测试用例随机失败,折腾了好久才发现是这个问题。
QFileInfo:文件属性的瑞士军刀
QFileInfo不直接操作文件内容,它专门用来获取文件的元信息。比如文件大小、创建时间、最后修改时间、文件权限、后缀名等等。
嗯,这里要注意一个细节:QFileInfo在构造时不会立即访问磁盘,它是在你第一次调用某个getter方法时才去获取信息。所以如果你频繁修改文件,记得调用refresh()方法更新缓存。
QFileInfo info("/home/user/document.txt");
if (info.exists()) {
qDebug() << "绝对路径:" << info.absoluteFilePath();
qDebug() << "基础名:" << info.baseName(); // "document"
qDebug() << "后缀名:" << info.suffix(); // "txt"
qDebug() << "完整后缀:" << info.completeSuffix(); // "txt"
qDebug() << "是否可读:" << info.isReadable();
qDebug() << "是否目录:" << info.isDir();
}
XML解析:QXmlStreamReader
XML虽然现在不如JSON流行,但在配置文件、文档格式(比如Office Open XML)里还是大量使用。Qt提供了两种XML解析方式:DOM(QDomDocument)和流式(QXmlStreamReader)。
我个人强烈推荐用QXmlStreamReader。为什么?因为它基于流,不会把整个XML文档加载到内存里。你想想看,如果遇到一个几百MB的XML文件,用DOM解析直接内存爆炸。而流式解析可以一边读一边处理,内存占用非常小。
QXmlStreamReader xml;
xml.setDevice(&file); // file是已经打开的QFile对象
while (!xml.atEnd() && !xml.hasError()) {
QXmlStreamReader::TokenType token = xml.readNext();
if (token == QXmlStreamReader::StartElement) {
if (xml.name() == "book") {
QString id = xml.attributes().value("id").toString();
qDebug() << "发现书籍, ID:" << id;
} else if (xml.name() == "title") {
QString title = xml.readElementText();
qDebug() << "书名:" << title;
}
}
}
if (xml.hasError()) {
qWarning() << "XML解析错误:" << xml.errorString();
}
避坑指南:我曾经在解析一个包含命名空间的XML时,直接用name()比较死活匹配不上。后来才发现,带命名空间的元素需要用qualifiedName()或者单独处理命名空间URI。记住,name()返回的是不带前缀的本地名,而qualifiedName()返回的是带前缀的完整名。
JSON解析:QJsonDocument
JSON现在是数据交换的事实标准。Qt的JSON支持非常完善,从Qt 5.0开始就内置了QJsonDocument、QJsonObject、QJsonArray、QJsonValue这几个类。
解析JSON的流程很简单:先把文本读进来,用QJsonDocument::fromJson()解析,然后根据顶层是对象还是数组,分别用object()或array()获取。
// 读取JSON文件
QFile file("data.json");
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "无法打开文件";
return;
}
QByteArray rawData = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(rawData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "JSON解析错误:" << parseError.errorString();
return;
}
if (doc.isObject()) {
QJsonObject obj = doc.object();
QString name = obj["name"].toString();
int age = obj["age"].toInt();
QJsonArray hobbies = obj["hobbies"].toArray();
qDebug() << "姓名:" << name << "年龄:" << age;
foreach (const QJsonValue &val, hobbies) {
qDebug() << "爱好:" << val.toString();
}
}
写JSON也很简单,构造好QJsonObject或QJsonArray,然后调用QJsonDocument的toJson()方法,最后写入文件。
// 构建JSON并写入文件
QJsonObject person;
person["name"] = "张三";
person["age"] = 28;
person["isStudent"] = false;
QJsonArray hobbies;
hobbies.append("编程");
hobbies.append("阅读");
hobbies.append("跑步");
person["hobbies"] = hobbies;
QJsonDocument doc(person);
QFile file("output.json");
if (file.open(QIODevice::WriteOnly)) {
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
}
关键对比:XML适合文档型数据(有属性、有嵌套、有注释),JSON适合结构化数据(轻量、易读、数组操作方便)。在实际项目中,我一般用JSON做配置文件,用XML做需要保留格式和注释的文档。
综合示例:文件配置管理器
最后,咱们把今天学的知识串起来,写一个简单的配置管理器。它支持从JSON或XML文件读取配置,也支持写入。
class ConfigManager {
public:
bool loadFromJson(const QString &filePath) {
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) return false;
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
file.close();
if (doc.isNull()) return false;
m_data = doc.object();
return true;
}
bool loadFromXml(const QString &filePath) {
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) return false;
QXmlStreamReader xml(&file);
m_data = QJsonObject(); // 清空
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "config") continue;
m_data[xml.name().toString()] = xml.readElementText();
}
}
file.close();
return !xml.hasError();
}
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const {
return m_data.value(key).toVariant(defaultValue);
}
private:
QJsonObject m_data;
};
这个类虽然简单,但已经能应付不少场景了。你可以在项目里直接拿来用,或者根据需求扩展。记住,文件操作的核心就三点:打开要检查、关闭要及时、路径要小心。把这三点记牢了,文件处理这块基本不会出大问题。