Qt 文件与目录操作详解:QFile, QDir, QFileInfo, 与 QTextStream
目录
1. QFileInfo - 文件信息查询器
核心知识点
2. QFile - 文件的读写通道
核心知识点
3. QDir - 目录操作与导航
核心知识点
4. QTextStream - 文本数据的便捷读写
核心知识点
5. 类之间的关系
6. 实际开发案例:日志文件管理器
C++ 示例代码 (需包含头文件)
案例讲解
7. 总结
在 Qt 开发中,与文件系统进行交互是不可避免的。无论是读写配置文件、创建日志、还是管理用户数据,你都需要一套可靠的工具。Qt 提供了 QFile、QDir、QFileInfo 和 QTextStream 这四个核心类,它们协同工作,为你提供了强大而便捷的文件与目录操作能力。
本文档将详细讲解这四个类的核心知识点、它们之间的关系,并通过一个实际案例来展示如何综合运用它们。
1. QFileInfo - 文件信息查询器
QFileInfo 是一个“只读”的类,它不用于读写文件内容,而是用来获取文件或目录的元数据(Metadata)。
核心知识点
-
用途:查询一个文件系统条目(文件、目录、符号链接等)的各种信息。
-
构造:通常通过一个文件或目录的路径字符串来构造。
-
关键函数:
-
exists(): 检查文件或目录是否存在。 -
path(): 返回完整路径(不含文件名)。 -
filePath(): 返回包含文件名的完整路径。 -
fileName(): 返回文件名(例如 "data.txt")。 -
baseName(): 返回不带后缀的文件名(例如 "data")。 -
suffix(): 返回文件的后缀(例如 "txt")。 -
completeSuffix(): 返回完整的后缀(例如 "tar.gz")。 -
size(): 返回文件大小(以字节为单位)。 -
isDir(): 判断这是否是一个目录。 -
isFile(): 判断这是否是一个文件。 -
isReadable()/isWritable()/isExecutable(): 检查权限。 -
created(): 返回创建时间(在某些系统上可能不可用)。 -
lastModified(): 返回最后修改时间。 -
lastRead(): 返回最后访问时间。
-
QFileInfo 非常轻量,你可以放心地创建它来快速检查文件的状态,而无需真正打开文件。
2. QFile - 文件的读写通道
QFile 是进行文件内容 I/O(输入/输出)操作的核心。它继承自 QIODevice,这意味着它是一个可以被读取或写入的设备。
核心知识点
-
用途:打开、关闭、读取和写入文件的内容。
-
构造:通过一个文件路径字符串来构造,或者后续使用
setFileName()。 -
核心流程:
-
设置文件名。
-
调用
open(QIODevice::OpenMode)打开文件,并指定模式(如ReadOnly,WriteOnly,ReadWrite,Append,Text)。 -
执行
read(),write(),readLine(),readAll()等操作。 -
调用
close()关闭文件。
-
-
关键函数:
-
open(QIODevice::OpenMode mode): 打开文件,这是最关键的一步。如果打开失败(例如权限不足或文件不存在),它会返回false。 -
close(): 关闭文件,释放文件句柄。 -
read(qint64 maxSize): 读取原始字节数据(QByteArray)。 -
write(const QByteArray &data): 写入原始字节数据。 -
exists(): 也可以用来检查文件是否存在。 -
remove(): 删除此文件。 -
copy(const QString &newName): 将此文件复制为新文件。 -
size(): 返回文件大小(只有打开的文件或已存在的文件才能获取)。 -
errorString(): 如果open()或其他操作失败,返回错误信息。
-
QFile 默认处理的是原始字节(QByteArray)。当你要处理文本时,通常会配合 QTextStream 使用。
3. QDir - 目录操作与导航
QDir 用于操作和导航目录(文件夹)。它用于创建、删除目录,以及列出目录中的内容。
核心知识点
-
用途:创建/删除目录、列出目录内容、路径操作。
-
构造:通过一个目录路径字符串构造,或使用
QDir::current()获取当前工作目录。 -
关键函数:
-
exists(): 检查目录是否存在。 -
mkdir(const QString &dirName): 在当前QDir路径下创建一个新目录。 -
mkpath(const QString &dirPath): 非常有用,可以创建多级不存在的父目录(例如logs/2025/10/)。 -
rmdir(const QString &dirName): 删除一个空目录。 -
removeRecursively(): 危险但有用,递归删除目录及其所有内容。 -
entryList(QDir::Filters filters = NoFilter, QDir::SortFlags sort = NoSort): 核心功能,返回目录中所有条目(文件和目录)的字符串列表(QStringList)。你可以使用过滤器(QDir::Files,QDir::Dirs,QDir::NoDotAndDotDot)来筛选。 -
entryInfoList(...): 与entryList类似,但返回的是QList<QFileInfo>,这更方便,因为你立刻获得了每个条目的详细信息。 -
filePath(const QString &fileName): 安全地将目录路径和文件名组合成一个完整的跨平台路径(例如,自动添加/或\)。 -
absolutePath(): 返回绝对路径。 -
cd(const QString &dirName): 切换到子目录。 -
cdUp(): 切换到父目录。
-
4. QTextStream - 文本数据的便捷读写
QTextStream 本身不直接与文件系统交互,它是一个辅助类,提供了一个方便的、流式的接口来读写文本数据。
核心知识点
-
用途:以文本形式(
QString、数字等)读写数据,自动处理字符编码。 -
构造:它必须**包装(Wrap)**一个
QIODevice。最常见的QIODevice就是QFile,但也可以是QByteArray(用于内存中的文本操作)。 -
核心优势:
-
流式操作符:可以使用
<<来写入,>>来读取,就像 C++ 的iostream。 -
编码处理:自动处理文本编码(默认为 UTF-8)。你可以使用
setCodec()来指定编码(例如QTextCodec::codecForName("GBK"))。 -
便捷函数:
-
readLine(): 读取一行文本(QString)。 -
readAll(): 读取所有剩余文本(QString)。
-
-
-
注意:
QTextStream拥有自己的内部缓冲区。在写入后,你可能需要调用flush()来确保数据立即写入到底层设备(如QFile),或者在QTextStream析构时它会自动flush。
5. 类之间的关系
这四个类共同构成了一个完整的文件操作生态系统:
-
QDir(目录) 发现QFile(文件)-
你使用
QDir来导航到一个目录(例如QDir dir("/var/logs"))。 -
然后你使用
dir.entryList()或dir.entryInfoList()来发现该目录下的文件。 -
你使用
dir.filePath("app.log")来构建一个文件的完整路径。
-
-
QFile(文件) 准备 I/O-
你将
QDir构建的路径传递给QFile的构造函数(例如QFile file(dir.filePath("app.log")))。 -
QFile负责open()和close()这个文件。
-
-
QTextStream(文本流) 包装QFile(文件)-
如果你要读写的是文本(而不是原始字节),你会创建一个
QTextStream。 -
你将
QFile对象的地址传递给QTextStream的构造函数(例如QTextStream stream(&file);)。 -
QTextStream接管了实际的读写操作,它通过QFile提供的QIODevice接口与文件通信,并为你处理编码和格式化。
-
-
QFileInfo(信息) 描述QFile(文件) 或QDir(目录)-
在打开
QFile之前,你可能会先用QFileInfo检查它:QFileInfo info(filePath); if (info.exists() && info.isReadable()) { ... }。 -
QDir::entryInfoList()会直接返回QFileInfo列表,让你在遍历目录时就能立即知道每个条目的大小、类型和日期。 -
你也可以用一个已打开的
QFile来构造QFileInfo:QFileInfo info(myFile);。
-
关系图(概念)
[ QDir ] -- (查找/构建路径) --> [ QString (路径) ]| || (entryInfoList) || |v v[ QFileInfo ] <-- (获取信息) -- [ QFile ] <-- (被包装) -- [ QTextStream ](查询元数据) (打开/关闭/读写字节) (读写格式化文本)
6. 实际开发案例:日志文件管理器
让我们结合所学知识,编写一个简单的日志记录函数,并附带一个日志清理功能。
需求:
-
确保日志目录
logs/存在,不存在则创建。 -
在
logs/目录下,打开或创建以当前日期命名的日志文件(例如2025-10-23.log)。 -
向该文件追加一条带时间戳的日志信息。
-
(额外)清理
logs/目录,删除所有超过7天的日志文件。
C++ 示例代码 (需包含头文件)
#include <QCoreApplication> // 假设在控制台应用中
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDateTime>
#include <QStringList>/*** @brief 向日志文件追加一条日志* @param message 日志消息*/
void writeLog(const QString &message) {// 1. 使用 QDir 确保目录存在QDir logDir(QCoreApplication::applicationDirPath() + "/logs");if (!logDir.exists()) {// mkpath 可以递归创建 "logs" 目录if (!logDir.mkpath(".")) {qWarning() << "无法创建日志目录:" << logDir.absolutePath();return;}}// 2. 构建文件名QString logFileName = QDate::currentDate().toString("yyyy-MM-dd") + ".log";// 使用 QDir::filePath 来安全地组合路径QString logFilePath = logDir.filePath(logFileName);// 3. 使用 QFile 打开文件QFile logFile(logFilePath);// 模式:WriteOnly (只写), Append (追加到末尾), Text (自动处理换行符)if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {qWarning() << "无法打开日志文件:" << logFilePath << " 错误:" << logFile.errorString();return;}// 4. 使用 QTextStream 包装 QFile 来写入文本QTextStream stream(&logFile);stream.setCodec("UTF-8"); // 确保使用 UTF-8 编码// 准备带时间戳的日志条目QString logEntry = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + " - " + message;stream << logEntry << "\n"; // 使用 << 运算符写入,并添加换行符// 5. 关闭文件// logFile.close(); // 当 stream 析构时,它会自动 flush()。// QFile 也会在析构时自动 close()。但显式 close() 是个好习惯。logFile.close(); qInfo() << "日志已写入:" << logFilePath;
}/*** @brief 清理旧的日志文件* @param daysToKeep 保留最近多少天的日志*/
void cleanOldLogs(int daysToKeep = 7) {QDir logDir(QCoreApplication::applicationDirPath() + "/logs");if (!logDir.exists()) {return; // 目录不存在,无需清理}// 计算N天前的日期QDateTime cutoffDate = QDateTime::currentDateTime().addDays(-daysToKeep);// 1. 使用 QDir::entryInfoList 获取所有日志文件的 QFileInfo// 过滤器:只看文件(Files),忽略"."和".." (NoDotAndDotDot)QList<QFileInfo> logFiles = logDir.entryInfoList(QStringList() << "*.log", QDir::Files | QDir::NoDotAndDotDot);qInfo() << "开始清理旧日志... 截止日期:" << cutoffDate.toString();int removedCount = 0;// 2. 遍历 QFileInfo 列表for (const QFileInfo &fileInfo : logFiles) {// 3. 使用 QFileInfo 检查修改日期if (fileInfo.lastModified() < cutoffDate) {qInfo() << "正在删除旧日志:" << fileInfo.fileName();// 4. 使用 QFile::remove 删除文件if (QFile::remove(fileInfo.filePath())) {removedCount++;} else {qWarning() << "无法删除文件:" << fileInfo.filePath();}}}qInfo() << "清理完成,共删除" << removedCount << "个旧日志文件。";
}// --- 示例调用 ---
// int main(int argc, char *argv[]) {
// QCoreApplication a(argc, argv);
//
// writeLog("应用程序启动。");
// writeLog("正在处理任务 A...");
// // ...
// writeLog("应用程序关闭。");
//
// cleanOldLogs(7); // 清理7天前的日志
//
// return 0;
// }
案例讲解
-
writeLog函数:-
QDir被用来检查logs目录是否存在,并使用mkpath创建它(如果需要)。mkpath比mkdir更健壮。 -
QDir::filePath()用来安全地将目录路径 (logDir) 和动态生成的文件名 (logFileName) 组合起来。 -
QFile承载这个路径,并使用open()以追加(Append)和文本(Text)模式打开。 -
QTextStream包装了logFile,允许我们使用<<运算符轻松写入格式化的QString(带时间戳的消息)。
-
-
cleanOldLogs函数:-
QDir再次登场,这次使用entryInfoList()。我们传递过滤器,只获取.log文件,并自动排除.和..。 -
这个函数直接返回
QList<QFileInfo>,这非常高效。 -
我们遍历这个列表,对每个
QFileInfo对象... -
...使用
lastModified()获取其时间戳,并与我们计算出的cutoffDate(截止日期)进行比较。 -
如果文件过时,我们从
QFileInfo中获取filePath(),并将其传递给QFile::remove()静态函数来删除该文件。
-
7. 总结
-
QDir:你的“地图”和“推土机”。用于查找、创建和列出目录。 -
QFileInfo:你的“侦察兵”。用于在不打开文件的情况下获取其所有信息(大小、日期、类型)。 -
QFile:你的“管道”。用于打开到文件的原始字节连接。 -
QTextStream:你的“翻译器”。用于将你的QString和数字“翻译”成字节(通过QFile)写入文件,或反向读取。
掌握这四个类的组合使用,是 Qt 中进行任何文件系统操作的基础。
