QIODevice类是Qt中所有I/O设备的基础接口类,为诸如QFile,QBuffer和QTcpSocket等支持读/写数据块的设备提供了一个抽象接口。QIODevice类是抽象的,无法被实例化,一般是使用它所定义的接口提供设备无关的I/O功能。
访问一个设备之前,需要使用open()函数打开该设备,而且必须制定正确的打开模式。QIODevice中所有的打开模式由QIODevice::OpenMode枚举类型定义,其可取值如下表所示:
QIODevice会区分两种类型的设备,随机存取设备和顺序存储设备:
可以在程序中使用isSequential()函数来判断设备的类型。
通过子类化QIODevice可以为自己的I/O设备提供相同的接口,要子类化QIODevice,则只需要重新实现readData()和writeData()两个函数。
QIODevice的一些子类,比如QFile和QTCPSocket,都使用了内存缓冲区进行数据的中间存储,这样减少了设备的访问次数,使得getChar()和putChar()等函数可以快速执行,而且可以在内存缓冲区上进行操作,而不用直接在设备上进行操作。但是,一些特定的I/O操作使用缓冲区却无法很好地工作,这时就可以在调用open()函数打开设备时使用QIODevice::Unbuffered模式来绕过所有的缓冲区。
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()来获取当前的位置。
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(); }
QTemporaryFile类是一个用来操作临时文件的I/O设备,它可以安全地创建一个唯一的临时文件。当调用open()函数时便会创建一个临时文件,临时文件的文件名可以保证是唯一的;当销毁QTemporaryFile对象时,该文件会被自动删除掉。在调用open()函数时,默认会使用QIODevice::ReadWrite模式,可以像下面的代码这样来使用QTemporaryFile类:
QTemporaryFile file; if(file.open()) { // 在这里对临时文件进行操作,file.fileName()可以返回唯一的文件名 }
调用了close()函数后重新打开QTemporaryFile是安全的,只有QTemporaryFile的对象没有被销毁,那么唯一的临时文件就会一直存在而且由QTemporaryFile内部保存打开。临时文件默认会生成在系统的临时目录里,这个目录的路径可以使用QDir::tempPath()来获取。
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类型的绝对路径。这些函数如下表所示:
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种方式:
QFile file("in.txt"); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine(); // 下面可以对读取的一行字符串进行处理 }
默认的,当从文本流中读取数字时,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和很多其他格式。