文件与数据
应用程序经常需要处理文件读写和数据持久化。Qt 提供了丰富的类来处理文件操作、JSON/XML 数据、应用设置等。本章将详细介绍这些功能的使用方法。
文件操作基础
QFile 读写文件
QFile 是 Qt 中最基础的文件操作类,支持文本和二进制文件的读写:
#include <QFile>
#include <QTextStream>
#include <QDebug>
// 读取文本文件
QString readTextFile(const QString &filePath)
{
QFile file(filePath);
// 以只读、文本模式打开文件
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "无法打开文件:" << file.errorString();
return QString();
}
// 读取全部内容
QString content = QString::fromUtf8(file.readAll());
file.close();
return content;
}
// 写入文本文件
bool writeTextFile(const QString &filePath, const QString &content)
{
QFile file(filePath);
// 以只写、文本模式打开文件(会覆盖原有内容)
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "无法打开文件:" << file.errorString();
return false;
}
// 写入内容
qint64 bytesWritten = file.write(content.toUtf8());
file.close();
return bytesWritten != -1;
}
// 使用示例
writeTextFile("test.txt", "Hello, Qt!\n这是测试内容。");
QString content = readTextFile("test.txt");
qDebug() << content;
使用 QTextStream
QTextStream 提供了更方便的文本流操作:
#include <QFile>
#include <QTextStream>
void textStreamExample()
{
QFile file("data.txt");
// 写入模式
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
// 设置编码(默认 UTF-8)
out.setEncoding(QStringConverter::Utf8);
// 写入数据
out << "姓名: 张三" << Qt::endl;
out << "年龄: 25" << Qt::endl;
out << "分数: " << 95.5 << Qt::endl;
file.close();
}
// 读取模式
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
// 逐行读取
while (!in.atEnd()) {
QString line = in.readLine();
qDebug() << line;
}
// 或者读取全部
// QString allContent = in.readAll();
file.close();
}
}
QTextStream 的优势:
- 自动处理编码转换
- 支持格式化输出(类似
printf) - 支持逐行读取,适合大文件
- 跨平台的行结束符处理
二进制文件操作
#include <QFile>
#include <QDataStream>
// 写入二进制数据
void writeBinaryFile(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly)) {
return;
}
QDataStream out(&file);
// 设置版本(确保跨版本兼容性)
out.setVersion(QDataStream::Qt_6_0);
// 写入基本类型
out << qint32(12345); // 整数
out << double(3.14159); // 浮点数
out << QString("测试字符串"); // 字符串
// 写入容器
QList<int> numbers = {1, 2, 3, 4, 5};
out << numbers;
// 写入自定义结构
struct Person {
QString name;
int age;
};
Person p = {"张三", 25};
out << p.name << p.age;
file.close();
}
// 读取二进制数据
void readBinaryFile(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_6_0);
// 按写入顺序读取
qint32 num;
double pi;
QString str;
QList<int> numbers;
in >> num >> pi >> str >> numbers;
qDebug() << "整数:" << num;
qDebug() << "浮点数:" << pi;
qDebug() << "字符串:" << str;
qDebug() << "列表:" << numbers;
file.close();
}
文件信息与操作
#include <QFileInfo>
#include <QDir>
void fileInfoExample()
{
QFileInfo info("data.txt");
// 文件属性
qDebug() << "文件名:" << info.fileName(); // data.txt
qDebug() << "完整路径:" << info.absoluteFilePath(); // /path/to/data.txt
qDebug() << "所在目录:" << info.absolutePath(); // /path/to
qDebug() << "文件大小:" << info.size() << "字节";
qDebug() << "是否可读:" << info.isReadable();
qDebug() << "是否可写:" << info.isWritable();
qDebug() << "是否存在:" << info.exists();
qDebug() << "是否是目录:" << info.isDir();
qDebug() << "是否是文件:" << info.isFile();
// 时间信息
qDebug() << "创建时间:" << info.birthTime().toString();
qDebug() << "修改时间:" << info.lastModified().toString();
qDebug() << "访问时间:" << info.lastRead().toString();
// 文件扩展名
qDebug() << "扩展名:" << info.suffix(); // txt
qDebug() << "完整扩展名:" << info.completeSuffix(); // tar.gz(如果是多层扩展名)
}
目录操作
#include <QDir>
void directoryExample()
{
QDir dir;
// 获取特殊目录
qDebug() << "当前目录:" << QDir::currentPath();
qDebug() << "用户目录:" << QDir::homePath();
qDebug() << "临时目录:" << QDir::tempPath();
qDebug() << "根目录:" << QDir::rootPath();
// 创建目录
if (!dir.exists("mydir")) {
dir.mkdir("mydir"); // 创建单级目录
// 或
dir.mkpath("mydir/subdir"); // 创建多级目录
}
// 列出目录内容
dir.setPath("mydir");
QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
qDebug() << "文件列表:" << files;
QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
qDebug() << "子目录:" << dirs;
// 过滤特定类型的文件
QStringList filters;
filters << "*.txt" << "*.md";
dir.setNameFilters(filters);
QStringList textFiles = dir.entryList();
// 遍历目录(包括子目录)
QDirIterator it("mydir", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next();
}
// 删除文件和目录
dir.remove("mydir/file.txt"); // 删除文件
dir.rmdir("mydir/subdir"); // 删除空目录
dir.removeRecursively(); // 递归删除目录及其内容(危险操作!)
}
JSON 处理
Qt 提供了完整的 JSON 支持库,可以方便地解析和生成 JSON 数据。
JSON 数据类型
JSON 支持以下基本数据类型:
- 布尔值:
true或false - 数值:整数或浮点数
- 字符串:Unicode 字符串
- 数组:有序值列表
- 对象:键值对集合
- 空值:
null
解析 JSON
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
void parseJsonExample()
{
// JSON 字符串
QString jsonStr = R"({
"name": "张三",
"age": 25,
"isStudent": true,
"scores": [85, 90, 78],
"address": {
"city": "北京",
"street": "长安街"
}
})";
// 从字符串解析
QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8());
if (doc.isNull()) {
qDebug() << "JSON 解析失败";
return;
}
// 获取根对象
QJsonObject root = doc.object();
// 读取基本类型
QString name = root["name"].toString();
int age = root["age"].toInt();
bool isStudent = root["isStudent"].toBool();
qDebug() << "姓名:" << name;
qDebug() << "年龄:" << age;
qDebug() << "是否学生:" << isStudent;
// 读取数组
QJsonArray scores = root["scores"].toArray();
qDebug() << "成绩:";
for (const QJsonValue &value : scores) {
qDebug() << " " << value.toInt();
}
// 读取嵌套对象
QJsonObject address = root["address"].toObject();
qDebug() << "城市:" << address["city"].toString();
qDebug() << "街道:" << address["street"].toString();
// 从文件读取 JSON
QFile file("data.json");
if (file.open(QIODevice::ReadOnly)) {
QJsonDocument fileDoc = QJsonDocument::fromJson(file.readAll());
file.close();
// ... 处理 JSON
}
}
生成 JSON
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
void generateJsonExample()
{
// 创建 JSON 对象
QJsonObject root;
root["name"] = "李四";
root["age"] = 30;
root["email"] = "[email protected]";
// 创建嵌套对象
QJsonObject address;
address["city"] = "上海";
address["district"] = "浦东新区";
address["zipCode"] = "200120";
root["address"] = address;
// 创建数组
QJsonArray hobbies;
hobbies.append("阅读");
hobbies.append("游泳");
hobbies.append("编程");
root["hobbies"] = hobbies;
// 创建 JSON 文档
QJsonDocument doc(root);
// 转换为字符串
QString jsonStr = doc.toJson(QJsonDocument::Indented); // 格式化输出
qDebug() << jsonStr;
// 紧凑输出
QString compactJson = doc.toJson(QJsonDocument::Compact);
// 保存到文件
QFile file("output.json");
if (file.open(QIODevice::WriteOnly)) {
file.write(doc.toJson());
file.close();
}
}
处理复杂的 JSON 结构
// 完整的配置文件示例
struct AppConfig {
QString appName;
QString version;
bool darkMode;
int fontSize;
QStringList recentFiles;
struct {
QString server;
int port;
int timeout;
} network;
};
// 从 JSON 读取配置
AppConfig loadConfig(const QString &filePath)
{
AppConfig config;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
// 返回默认配置
config.appName = "MyApp";
config.version = "1.0.0";
return config;
}
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
QJsonObject root = doc.object();
config.appName = root["appName"].toString("MyApp");
config.version = root["version"].toString("1.0.0");
config.darkMode = root["darkMode"].toBool(false);
config.fontSize = root["fontSize"].toInt(12);
// 读取字符串数组
QJsonArray recentFiles = root["recentFiles"].toArray();
for (const QJsonValue &value : recentFiles) {
config.recentFiles.append(value.toString());
}
// 读取嵌套对象
QJsonObject network = root["network"].toObject();
config.network.server = network["server"].toString("localhost");
config.network.port = network["port"].toInt(8080);
config.network.timeout = network["timeout"].toInt(30);
return config;
}
// 保存配置到 JSON
void saveConfig(const QString &filePath, const AppConfig &config)
{
QJsonObject root;
root["appName"] = config.appName;
root["version"] = config.version;
root["darkMode"] = config.darkMode;
root["fontSize"] = config.fontSize;
// 写入字符串数组
QJsonArray recentFiles;
for (const QString &file : config.recentFiles) {
recentFiles.append(file);
}
root["recentFiles"] = recentFiles;
// 写入嵌套对象
QJsonObject network;
network["server"] = config.network.server;
network["port"] = config.network.port;
network["timeout"] = config.network.timeout;
root["network"] = network;
QJsonDocument doc(root);
QFile file(filePath);
if (file.open(QIODevice::WriteOnly)) {
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
}
}
XML 处理
Qt 提供了两种 XML 处理方式:DOM 方式(QDomDocument)和流式方式(QXmlStreamReader/Writer)。
流式读取 XML
#include <QXmlStreamReader>
#include <QFile>
void readXmlExample()
{
QString xmlContent = R"(
<library>
<book id="1">
<title>Qt 学习指南</title>
<author>张三</author>
<price>59.00</price>
</book>
<book id="2">
<title>C++ 入门</title>
<author>李四</author>
<price>45.00</price>
</book>
</library>
)";
QXmlStreamReader xml(xmlContent);
// 或从文件读取
// QFile file("books.xml");
// file.open(QIODevice::ReadOnly);
// QXmlStreamReader xml(&file);
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") {
qDebug() << "标题:" << xml.readElementText();
}
else if (xml.name() == "author") {
qDebug() << "作者:" << xml.readElementText();
}
else if (xml.name() == "price") {
qDebug() << "价格:" << xml.readElementText();
}
}
}
if (xml.hasError()) {
qDebug() << "XML 错误:" << xml.errorString();
}
}
流式写入 XML
#include <QXmlStreamWriter>
#include <QFile>
void writeXmlExample()
{
QFile file("output.xml");
if (!file.open(QIODevice::WriteOnly)) {
return;
}
QXmlStreamWriter xml(&file);
xml.setAutoFormatting(true); // 自动格式化
xml.setAutoFormattingIndent(4); // 缩进 4 空格
xml.writeStartDocument(); // <?xml version="1.0" encoding="UTF-8"?>
xml.writeStartElement("library"); // <library>
// 第一本书
xml.writeStartElement("book"); // <book>
xml.writeAttribute("id", "1"); // id="1"
xml.writeTextElement("title", "Qt 学习指南");
xml.writeTextElement("author", "张三");
xml.writeTextElement("price", "59.00");
xml.writeEndElement(); // </book>
// 第二本书
xml.writeStartElement("book");
xml.writeAttribute("id", "2");
xml.writeTextElement("title", "C++ 入门");
xml.writeTextElement("author", "李四");
xml.writeTextElement("price", "45.00");
xml.writeEndElement();
xml.writeEndElement(); // </library>
xml.writeEndDocument(); // 结束文档
file.close();
}
DOM 方式处理 XML
#include <QDomDocument>
#include <QDomElement>
#include <QFile>
void domXmlExample()
{
// 创建 DOM 文档
QDomDocument doc;
// 读取文件
QFile file("books.xml");
if (file.open(QIODevice::ReadOnly)) {
if (!doc.setContent(&file)) {
file.close();
return;
}
file.close();
}
// 获取根元素
QDomElement root = doc.documentElement();
qDebug() << "根元素:" << root.tagName();
// 遍历子元素
QDomNodeList books = root.elementsByTagName("book");
for (int i = 0; i < books.count(); i++) {
QDomElement book = books.at(i).toElement();
QString id = book.attribute("id");
QString title = book.firstChildElement("title").text();
QString author = book.firstChildElement("author").text();
qDebug() << "ID:" << id << "标题:" << title << "作者:" << author;
}
// 修改 DOM
QDomElement newBook = doc.createElement("book");
newBook.setAttribute("id", "3");
QDomElement title = doc.createElement("title");
title.appendChild(doc.createTextNode("新书"));
newBook.appendChild(title);
root.appendChild(newBook);
// 保存修改
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << doc.toString();
file.close();
}
}
DOM vs 流式处理:
| 特性 | DOM (QDomDocument) | 流式 (QXmlStreamReader) |
|---|---|---|
| 内存占用 | 整个文档加载到内存 | 逐节点处理,内存占用小 |
| 修改能力 | 支持随机访问和修改 | 只能顺序读取 |
| 性能 | 大文件较慢 | 大文件性能好 |
| 适用场景 | 小文件、需要修改 | 大文件、只读取 |
应用设置存储
QSettings 提供了简单的应用设置持久化机制,自动处理不同平台的存储位置。
基本使用
#include <QSettings>
void settingsExample()
{
// 创建设置对象(组织名 + 应用名)
QSettings settings("MyCompany", "MyApp");
// 写入设置
settings.setValue("window/size", QSize(800, 600));
settings.setValue("window/position", QPoint(100, 100));
settings.setValue("user/name", "张三");
settings.setValue("user/rememberPassword", true);
settings.setValue("recentFiles", QStringList() << "file1.txt" << "file2.txt");
// 读取设置(带默认值)
QSize size = settings.value("window/size", QSize(400, 300)).toSize();
QString name = settings.value("user/name", "默认用户").toString();
bool remember = settings.value("user/rememberPassword", false).toBool();
QStringList files = settings.value("recentFiles").toStringList();
qDebug() << "窗口大小:" << size;
qDebug() << "用户名:" << name;
qDebug() << "记住密码:" << remember;
// 检查设置是否存在
if (settings.contains("user/name")) {
// ...
}
// 删除设置
settings.remove("user/name");
// 清空所有设置
// settings.clear();
// 获取所有键
QStringList keys = settings.allKeys();
qDebug() << "所有设置:" << keys;
}
设置分组
void settingsGroupExample()
{
QSettings settings("MyCompany", "MyApp");
// 方式 1:使用斜杠分隔
settings.setValue("editor/fontFamily", "Consolas");
settings.setValue("editor/fontSize", 12);
settings.setValue("editor/tabSize", 4);
// 方式 2:使用 beginGroup/endGroup
settings.beginGroup("editor");
settings.setValue("fontFamily", "Consolas");
settings.setValue("fontSize", 12);
settings.setValue("tabSize", 4);
// 嵌套分组
settings.beginGroup("colors");
settings.setValue("background", "#FFFFFF");
settings.setValue("foreground", "#000000");
settings.endGroup(); // 结束 colors 分组
settings.endGroup(); // 结束 editor 分组
// 读取分组设置
settings.beginGroup("editor");
QString fontFamily = settings.value("fontFamily").toString();
settings.endGroup();
}
平台特定的存储位置
不同平台下,QSettings 的存储位置不同:
| 平台 | 存储位置 |
|---|---|
| Windows | 注册表 HKEY_CURRENT_USER\Software\MyCompany\MyApp |
| macOS | ~/Library/Preferences/com.MyCompany.MyApp.plist |
| Linux | ~/.config/MyCompany/MyApp.conf |
如果希望使用 INI 文件格式(跨平台一致),可以指定格式:
// 使用 INI 文件格式
QSettings settings("config.ini", QSettings::IniFormat);
// 自定义存储路径
QString configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
QSettings settings(configPath + "/settings.ini", QSettings::IniFormat);
应用退出时自动保存窗口状态
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
// 恢复窗口状态
QSettings settings("MyCompany", "MyApp");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
}
protected:
void closeEvent(QCloseEvent *event) override
{
// 保存窗口状态
QSettings settings("MyCompany", "MyApp");
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
QMainWindow::closeEvent(event);
}
};
标准路径
Qt 提供了获取系统标准路径的方法:
#include <QStandardPaths>
void standardPathsExample()
{
// 获取各种标准路径
QString desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
QString documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QString downloads = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
QString appData = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QString temp = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
qDebug() << "桌面:" << desktop;
qDebug() << "文档:" << documents;
qDebug() << "下载:" << downloads;
qDebug() << "应用数据:" << appData;
qDebug() << "临时:" << temp;
// 获取可执行文件路径
QString exePath = QCoreApplication::applicationDirPath();
// 获取应用数据目录(适合存储配置文件)
QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
// 确保目录存在
QDir dir(configDir);
if (!dir.exists()) {
dir.mkpath(".");
}
}
小结
本章介绍了 Qt 中文件与数据处理的各个方面:
- 文件操作:QFile、QTextStream、QDataStream 的使用
- 目录操作:QDir 和 QFileInfo 的使用
- JSON 处理:QJsonDocument、QJsonObject、QJsonArray 的解析和生成
- XML 处理:流式处理和 DOM 处理两种方式
- 应用设置:QSettings 实现配置持久化
- 标准路径:获取系统标准目录位置
掌握这些功能,可以满足大多数应用程序的数据持久化需求。
练习
- 实现一个日志记录类,支持写入日志到文件,并按日期自动分割文件
- 创建一个简单的配置管理器,使用 JSON 存储和读取应用配置
- 实现一个文件搜索工具,支持按扩展名过滤和递归搜索
- 使用 XML 存储和读取通讯录数据
- 实现一个最近打开文件列表功能,使用 QSettings 存储