跳到主要内容

文件与数据

应用程序经常需要处理文件读写和数据持久化。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 支持以下基本数据类型:

  • 布尔值truefalse
  • 数值:整数或浮点数
  • 字符串: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 中文件与数据处理的各个方面:

  1. 文件操作:QFile、QTextStream、QDataStream 的使用
  2. 目录操作:QDir 和 QFileInfo 的使用
  3. JSON 处理:QJsonDocument、QJsonObject、QJsonArray 的解析和生成
  4. XML 处理:流式处理和 DOM 处理两种方式
  5. 应用设置:QSettings 实现配置持久化
  6. 标准路径:获取系统标准目录位置

掌握这些功能,可以满足大多数应用程序的数据持久化需求。

练习

  1. 实现一个日志记录类,支持写入日志到文件,并按日期自动分割文件
  2. 创建一个简单的配置管理器,使用 JSON 存储和读取应用配置
  3. 实现一个文件搜索工具,支持按扩展名过滤和递归搜索
  4. 使用 XML 存储和读取通讯录数据
  5. 实现一个最近打开文件列表功能,使用 QSettings 存储

参考资源