从零开始学Qt之文件与目录操作


  应用程序中经常需要对设备或者文件进行读取或写入,也经常会对本地文件系统中的文件或者目录进行操作。

文件和目录

输入/输出设备

  QIODevice类是Qt中所有I/O设备的基础接口类,为诸如QFile,QBuffer和QTcpSocket等支持读/写数据块的设备提供了一个抽象接口。QIODevice类是抽象的,无法被实例化,一般是使用它所定义的接口提供设备无关的I/O功能。

  访问一个设备之前,需要使用open()函数打开该设备,而且必须制定正确的打开模式。QIODevice中所有的打开模式由QIODevice::OpenMode枚举类型定义,其可取值如下表所示:

请添加图片描述

QIODevice会区分两种类型的设备,随机存取设备和顺序存储设备:

  • 随机存取设备支持使用seek()函数来定位到任意的位置。文件中的当前位置可以使用pos()函数来获取。这样的随机存取设备有QFile,QBuffer等。
  • 顺序存储设备不支持定位到任意的位置,数据必须一次性读取。pos()和size()等函数无法在操作顺序设备时使用,这样的顺序存储设备有QTcpSocket,QProcess等。

可以在程序中使用isSequential()函数来判断设备的类型。

  通过子类化QIODevice可以为自己的I/O设备提供相同的接口,要子类化QIODevice,则只需要重新实现readData()和writeData()两个函数。

  QIODevice的一些子类,比如QFile和QTCPSocket,都使用了内存缓冲区进行数据的中间存储,这样减少了设备的访问次数,使得getChar()和putChar()等函数可以快速执行,而且可以在内存缓冲区上进行操作,而不用直接在设备上进行操作。但是,一些特定的I/O操作使用缓冲区却无法很好地工作,这时就可以在调用open()函数打开设备时使用QIODevice::Unbuffered模式来绕过所有的缓冲区。

文件操作

1. 文件QFile

  QFile类提供了一个用于读/写文件的接口,它是一个可以用于读/写文本文件,二进制文件和Qt资源的I/O设备。QFile可以单独使用,也可以和QTextStream或者QDataStream一起使用这样会更方便。

  一般在构建QFile对象时便指定文件名,当前也可以使用setFileName()进行设置,无论在哪种操作系统上,文件名路径中的文件分隔符都需要使用"/"符号。可以使用exists()来检查文件是否存在,使用remove()删除一个文件。

  一个文件可以使用open()打开,使用close()来关闭,使用flush()刷新。文件的数据读/写一般使用QDataStream或者QTextStream来完成,不过也可以使用继承自QIODevice类的一些函数,比如read(),readLine(),readAll()和write()。

  还有一次只操作一个字符的getChar(),putChar()和ungetChar()等函数。可以使用size()函数文件的大小,使用seek()来定位到文件的任意位置,使用pos()来获取当前的位置。

2. 文件信息 QFileInfo

  QFileInfo类提供了与系统无关的文件信息,包括文件的名称,在文件系统中的位置(路径),文件的访问权限以及是否是一个目录或者符号链接等。QFileInfo也可以获取文件的大小和最近一次修改/读取的时间。

  QFileInfo可以使用相对(relative)路径或者绝对(absolute)路径来指向一个文件,使用isRelative()函数可以判断一个QFileInfo对象使用的是相对路径还是绝对路径,还可以使用makeAbsolute()来将一个相对路径转换为绝对路径。

   FileInfo指向的文件可以在 FileInfo对象构建时设置,或者以后使用setFile()来设置。可以使用exists()来查看文件是否存在,使用size()获取文件的大小。文件的类型可以使用isFile()、isDir()和isSymLink()来获取,symLinkTarget()函数可以返回符号链接指向的文件的名称。

  可以分别使用path()和fileName()来获取文件的路径和文件名,还可以使用baseName()来获取文件名中的基本名称,使用suffix()来获取文件名的后缀,使用completeSuffix()来获取复合后缀。文件的日期可以使用created()、lastModified()和lastRead()来返回;访问权限可以使用isReadable()、isWritable()和inExecutable()来获取;文件的所有权可以使用owner()、ownerId()、group()和grouped()来获取;还可以使用permission()函数将文件的访问权限和所有权一次性读取出来。

下面通过一个例子来演示这些知识点。

  新建Qt控制台应用(Qt Console Application),项目名称为myfile,创建完成后将main.cpp文件的内容更改为:

#include <QCoreApplication>
#include <QFileInfo>
#include <QStringList>
#include <QDateTime>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QFile file("../myfile/test.txt");
    // 以只写方式打开文件,如果文件不存在,那么会自动创建该文件
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
        qDebug() << file.errorString();
    file.write("Hello Qt!\n");  // 向文件中写入字符串"Hello Qt!\n"
    QTextStream out(&file);     // 创建一个[输入到file的]文本流对象
    out << "some Data!";
    file.close();

    // 获取文件信息
    QFileInfo info(file);   // 文件file的信息
    qDebug() << QObject::tr("绝对路径:") << info.absoluteFilePath() << endl
             << QObject::tr("文件名:") << info.fileName() << endl
             << QObject::tr("基本名称:") << info.baseName() << endl
             << QObject::tr("后缀:") << info.suffix() << endl
             << QObject::tr("创建时间:") << info.created() << endl
             << QObject::tr("大小:") << info.size();

    // 以只读方式打开文件,如果文件不存在,那么会自动创建该文件
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
        qDebug() << file.errorString();
    qDebug() << QObject::tr("文件内容:") << endl << file.readAll();
    qDebug() << QObject::tr("当前日期:") << file.pos();
    file.seek(15);

    QByteArray array;
    array = file.read(5);	// 向文件中读入5个字符
    qDebug() << QObject::tr("前5个字符:") << array << QObject::tr("当前位置:") << file.pos();

    file.seek(15);			// 读取指针移动到15个字符之后
    array = file.read(5);
    qDebug() << QObject::tr("第16-20个字符:") << array;
    file.close();			// 每一个open()对应着文件的close()
    return a.exec();
}

请添加图片描述

3. 临时文件QTemporaryFile

  QTemporaryFile类是一个用来操作临时文件的I/O设备,它可以安全地创建一个唯一的临时文件。当调用open()函数时便会创建一个临时文件,临时文件的文件名可以保证是唯一的;当销毁QTemporaryFile对象时,该文件会被自动删除掉。在调用open()函数时,默认会使用QIODevice::ReadWrite模式,可以像下面的代码这样来使用QTemporaryFile类:

QTemporaryFile file;
if(file.open()) {
	// 在这里对临时文件进行操作,file.fileName()可以返回唯一的文件名
}

  调用了close()函数后重新打开QTemporaryFile是安全的,只有QTemporaryFile的对象没有被销毁,那么唯一的临时文件就会一直存在而且由QTemporaryFile内部保存打开。临时文件默认会生成在系统的临时目录里,这个目录的路径可以使用QDir::tempPath()来获取。

目录操作

1. 目录QDir

  QDir类用来访问目录结构及其内容,可以操作路径名,访问路径和文件相关信息,操作底层的文件系统,还可以访问Qt的资源系统。Qt使用"/“作为通用的目录分隔符和URLs的目录分隔符,如果使用”/"作为目录分隔符,则Qt会自动转换路径来适应底层的操作系统。QDir可以使用相对路径或者绝对路径来指向一个文件。

使用绝对路径的例子:

QDir("/home/user/Documents")
QDir("C:/Documents/setting");	// 在Windows上使用时,会被转换为"C:\Documents\setting"

使用相对路径的例子:

QDir("images/landscape.png")

  可以使用isRelative()和isAbsolute()来判断一个QDir是否使用了相对路径或者绝对路径,还可以使用makeAbsolute()来将一个相对路径转换为绝对路径。

  一个目录的路径可以使用path()函数获取,使用setPath()函数可以设置新的路径,使用absolutePath()函数可以获取绝对路径。目录名可以使用dirName()函数获取,这通常返回绝对路径中的最后一个元素;然而如果QDir代表当前目录,那么会返回“.”。

  目录的路径也可以使用cd()和cdUp()函数来改变,当使用一个存在的目录的名字来调用cd()时,QDir对象就会转换到指定的目录;而cdUp()会跳转到父目录,cdUp()与cd(“…”)是等效的。

  可以使用mkdir()来创建目录,使用rename()进行重命名,使用rmdir()删除目录。可以使用exists()函数来测试指定的目录是否存在,使用isReadable()和isRoot()等函数来测试目录的属性。使用refresh()函数可以重新读取目录的数据。

  目录中会包含很多条目,如文件、目录和符号链接等。一个目录中的条目数目可以使用count()来返回,所有条目的名称列表可以使用entryList()来获取;如果需要每一个条目的信息,则可以使用entryInfoList()函数来获取一个QFileInfo对象的列表。

  可以使用filePath()和absoluteFilepath()来获取一个目录中的文件和目录的路径,filePath()会返回指定文件或目录与当前QDir对象所在路径的相对路径,而absoluteFilePath()会返回绝对路径。文件可以使用remove()函数来移除,但是目录只能使用rmdir()函数来移除。

  可以应用一个名称过滤器(name filters)来使用通配符(wildcards)指定一个模式进行文件名的匹配,一个属性过滤器可以选取条目的属性并且可以区分文件和目录,还可以设定排序顺序。名称过滤器就是一个字符串列表,可以使用setNamefilters()函数来设置。

  例如,下面的代码在QDir上使用了3个名称过滤器来确保只有以通常用于C++源文件的扩展名结尾的文件才会被列出:

QStringList filters;
filter << "*.cpp" << "*.cxx" << "*.cc";
dir.setNameFilters(filters);

  属性过滤器由按位或组合在一起的过滤器组成,可以使用setFilter()来设置。排序顺序使用setSorting()来设置,它需要指定按位或组合在一起的排序标志QDir::SortFlags,所有的排序标志可以在QDir的帮助文档中查看。

  可以使用match()函数来测试一个文件是否匹配一个过滤器,设置好过滤器和排序标志后,就可以调用entryList()或者entryInfoList()来获取指定条件的条目了。

  要访问一些常见的目录,则可以使用一些静态函数来完成,它们可以返回QDir对象或者QString类型的绝对路径。这些函数如下表所示:
请添加图片描述

2. 文件系统监视器 QFileSystemWatcher

  QFileSystemWatcher类提供了一个接口来监控文件和目录的修改,通过监视一个指定路径的列表来监控文件系统中文件和目录的改变。调用 addPath()来监视一个指定的文件或者目录,多个路径可以使用addPaths()函数来添加,现有的路径可以使用removePath()和removePaths()函数来移除。QFileSystemWatcher会检测每一个添加到它上面的路径,添加到其上的文件的路径可以使用files()来获取,目录的路径可以使用 directories()函数来获取。

  当文件被修改、重命名或者移除后,会发射fileChanged()信号。相似的,当目录或者它的内容被修改或者移除后,会发射directoryChanged()信号。需要注意的是,当文件被重命名或者移除后,或者当目录被移除后,QFileSystemWatcher都会停止监视。

  新建 Qt Widgets应用,项目名称为mydir,基类选择QMainWindow,类名为MainWindow。项目创建后首先双击mainwindow.ui文件进入设计模式,往界面上拖入一个 ListWidget部件。

然后到mainwindow.h文件中添加头文件,再添加一个私有对象:

QFileSystemWatcher myWatcher;

然后添加一个私有槽声明:

private slots:
	void showMessage(const QString &path);

到mainwindow.cpp文件中添加头文件,然后在构造函数中添加如下代码:

// 将监视器信号与自定义的信息显示函数相关联
connect(&myWatcher, &QFileSystemWatcher::directoryChanged, this, &mainwindow::showMessage);
connect(&myWatcher, &QFileSystemWatcher::fileChanged, this, &mainwindow::showMessage);

QDir myDir(QDir::currentPath());
myDir.setNameFilters(QStringList("*.h"));
ui->listWidget->addItem(myDir.absolutePath() + tr("目录下的.h文件有"));
ui->listWidget->addItems(myDir.entryList());

myDir.mkdir("mydir");
myDir.cd("mydir");
ui->listWidget->addItem(tr("监视的目录:") + myDir.absolutePath());
myWatcher.addPath(myDir.absolutePath());

QFile file(myDir.absolutePath() + "/myfile.txt");
if(file.open(QIODevice::WriteOnly)) {
    QFileInfo info(file);
    ui->listWidget->addItem(tr("监视的文件:") + info.absoluteFilePath());
    myWatcher.addPath(info.absoluteFilePath());
    file.close();

然后添加showMessage()函数的定义:

void mainwindow::showMessage(const QString &path) {
    QDir dir(QDir::currentPath() + "/mydir");
    if(path == dir.absolutePath()) {
        ui->listWidget->addItem(dir.dirName() + tr("目录发生改变:"));
        ui->listWidget->addItems(dir.entryList());
    } else {
        ui->listWidget->addItem(path + tr("文件发生改变"));
    }
}

文本流和数据流

使用文本流读/写文本文件

  QTextStream类提供了一个方便的接口来读/写文本,可以在QIODevice,QByteArray和QString上进行操作。使用QTextStream的流操作符,可以方便地读/写单词,行和数字。对于生成文本,QTextStream对字段填充,对齐和数字格式提供了格式选项支持。例如:

QFile data("output.txt");
if(data.open(QFile::WriteOnly | QFile::Truncate)) {
	QTextStream out(&data);
	// 写入Result: 3.14 	2.7 	"
	out << "Result: " << qSetFieldWidth(10) << left << 3.14 << 2.7;
}

  QTextStream提供的格式选项可以在该类的帮助文档中看到。

  除了使用QTextStrean类的构造函数来设置设备外,还可以使用 setDevice()或者setString()来设置QTextStream要操作的设备或者字符串。可以使用seek()来定位到一个指定位置,使用atEnd()判断是否还有可以读取的数据。如果调用了flush()函数,则QTextStream会清空写缓冲中的所有数据,并且调用设备的 flush()函数。

  在内部,QTextStream使用了一个基于Unicode的缓冲区,QTextStream使用
QTextCodec来自动支持不同的字符集。默认的,使用QTextCodec::codecForLocale()返回的编码来进行读/写,也可以使用setcodec()函数来设置编码。

使用QTextStream来读取文本文件一般使用3种方式:

  1. 调用readLine()或者readALL()进行一块接着一块的读取。例如,下面代码实现了对文本文件进行分行读取:
QFile file("in.txt");
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
	return;
QTextStream in(&file);
while(!in.atEnd()) {
	QString line = in.readLine();
	// 下面可以对读取的一行字符串进行处理
}
  1. 一个单词接着一个单词。QTextStream支持流入到QString,QByteArray和char * 缓冲区,单词由空格分开,而且可以自动跳过前导空格。
  2. 一个字符接着一个字符,使用QChar或者char类型的流。这种方式经常在解析文件,使用独立的字符编码和行结束语义时用于方便输入处理。可以通过调用skipWhiteSpace()来跳过空格。

  默认的,当从文本流中读取数字时,QTextStream会自动检测数字的基数表示。例如,如果数字以"0x"开头,则它将被假定为16进制形式;如果以数字1 ~ 9开头,那么它将被假定为十进制形式等。也可以使用dec等流操作符或者setIntegerBase()来设置整数基数,从而停止自动检测。

使用数据流读/写二进制数据

  QDataStream类实现了将QIODevice的二进制数据串行化。一个数据流就是一个二进制编码信息流,它完全独立于主机的操作系统,CPU和字节顺序。数据流也可以读/写未编码的原始二进制数据。

  QDataStream类可以实现C++基本数据类型的串行化,比如char,short,int和char* 等。串行化更复杂的数据是通过将数据分解为基本的数据类型来完成的。

将二进制数据写入到数据流中:

QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDateStream out(&file);	// 将串行后的数据输入到file中
out << QString("the answer is");	// 串行化字符串
out << (qint32)42;		// 串行化整数

从数据流中读取二进制数据:

QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDateStream out(&file);	// 从file中读取串行化的数据
QString str;
qint32 a;
in >> str >> a;			// 提取"the answer is"和42

  写入到数据流中的每一个条目都是使用一个预定义的格式写入的,这个格式依赖于条目的类型。支持的Qt类型包括QBrush,QColor,QDateTime,QFont,QPixmap,QString,QVariant和很多其他格式。

猜你喜欢

转载自blog.csdn.net/qq_39153720/article/details/121219547