文件操作是应用程序必不可少的部分。Qt 作为一个通用开发库,提供了跨平台的文件操作能力。Qt 通过QIODevice提供了对 I/O 设备的抽象,这些设备具有读写字节块的能力。下面是 I/O 设备的类图(Qt5):
文件系统分类:
是指它们的数据只能访问一遍:从头走到尾,从第一个字节开始访问,直到最后一个字节,中途不能返回去读取上一个字节,这其中,QProcess、QTcpSocket、QUdpSoctet和QSslSocket是顺序访问设备。
可以访问任意位置任意次数,还可以使用QIODevice::seek()函数来重新定位文件访问位置指针,QFile、QTemporaryFile和QBuffer是随机访问设备,
在所有的 I/O 设备中,文件 I/O 是最重要的部分之一。因为我们大多数的程序依旧需要首先访问本地文件(当然,在云计算大行其道的将来,这一观点可能改变)。QFile提供了从文件中读取和写入数据的能力。
我们通常会将文件路径作为参数传给QFile的构造函数。不过也可以在创建好对象最后,使用setFileName()来修改。QFile需要使用 / 作为文件分隔符,不过,它会自动将其转换成操作系统所需要的形式。例如 C:/windows 这样的路径在 Windows 平台下同样是可以的。
QFile主要提供了有关文件的各种操作,比如打开文件、关闭文件、刷新文件等。我们可以使用QDataStream或QTextStream类来读写文件,也可以使用QIODevice类提供的read()、readLine()、readAll()以及write()这样的函数。值得注意的是,有关文件本身的信息,比如文件名、文件所在目录的名字等,则是通过QFileInfo获取,而不是自己分析文件路径字符串。
下面我们使用一段代码来看看QFile的有关操作:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFile file("in.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Open file failed.";
return -1;
} else {
while (!file.atEnd()) {
qDebug() << file.readLine();
}
}
QFileInfo info(file);
qDebug() << info.isDir();
qDebug() << info.isExecutable();
qDebug() << info.baseName();
qDebug() << info.completeBaseName();
qDebug() << info.suffix();
qDebug() << info.completeSuffix();
return app.exec();
}
这个文件名字是 in.txt。如果你不知道应该把它放在哪里,可以使用QDir::currentPath()来获得应用程序执行时的当前路径。只要将这个文件放在与当前路径一致的目录下即可。
这个类似于fopen()的 r 这样的参数。open()函数返回一个 bool 类型,如果打开失败,我们在控制台输出一段提示然后程序退出。否则,我们利用 while 循环,将每一行读到的内容输出。
QFileInfo有很多类型的函数,我们只举出一些例子。比如:
我们可以由下面的示例看到,baseName()和completeBaseName(),以及suffix()和completeSuffix()的区别:
QFileInfo fi("/tmp/archive.tar.gz");
QString base = fi.baseName(); // base = "archive"
QString base = fi.completeBaseName(); // base = "archive.tar"
QString ext = fi.suffix(); // ext = "gz"
QString ext = fi.completeSuffix(); // ext = "tar.gz"
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_buttonRead_clicked()
{
QString path = QFileDialog::getOpenFileName(this,
"open", "../", "TXT(*.txt)");
if(path.isEmpty() == false)
{
//文件对象
QFile file(path);
//打开文件,只读方式
bool isOk = file.open(QIODevice::ReadOnly);
if(isOk == true)
{
#if 0
//读文件,默认只识别utf8编码
QByteArray array = file.readAll();
//显示到编辑区
//ui->textEdit->setText(QString(array));
ui->textEdit->setText(array);
#endif
QByteArray array;
while( file.atEnd() == false)
{
//读一行
array += file.readLine();
}
ui->textEdit->setText(array);
}
//关闭文件
file.close();
//获取文件信息
QFileInfo info(path);
qDebug() << "文件名字:" << info.fileName().toUtf8().data();
qDebug() << "文件后缀:" << info.suffix();
qDebug() << "文件大小:" << info.size();
qDebug() << "文件创建时间:" <<
info.created().toString("yyyy-MM-dd hh:mm:ss"); //2016-01-04 15:13:00
}
}
void Widget::on_buttonWrite_clicked()
{
QString path = QFileDialog::getSaveFileName(this, "save", "../", "TXT(*.txt)");
if(path.isEmpty() == false)
{
QFile file; //创建文件对象
//关联文件名字
file.setFileName(path);
//打开文件,只写方式
bool isOk = file.open(QIODevice::WriteOnly);
if(isOk == true)
{
//获取编辑区内容
QString str = ui->textEdit->toPlainText();
//写文件
// QString -> QByteArray
//file.write(str.toUtf8());
//QString -> c++ string -> char *
//file.write(str.toStdString().data());
//转换为本地平台编码
file.write(str.toLocal8Bit());
//QString -> QByteArray
QString buf = "123";
QByteArray a = buf.toUtf8(); //中文
a = buf.toLocal8Bit(); //本地编码
//QByteArray -> char *
char *b = a.data();
//char * -> QString
char *p = "abc";
QString c = QString(p);
}
file.close();
}
}
QDataStream提供了基于QIODevice的二进制数据的序列化。数据流是一种二进制流,这种流完全不依赖于底层操作系统、CPU 或者字节顺序(大端或小端)。例如,在安装了 Windows 平台的 PC 上面写入的一个数据流,可以不经过任何处理,直接拿到运行了 Solaris 的 SPARC 机器上读取。由于数据流就是二进制流,因此我们也可以直接读写没有编码的二进制数据,例如图像、视频、音频等。
QDataStream既能够存取 C++ 基本类型,如 int、char、short 等,也可以存取复杂的数据类型,例如自定义的类。实际上,QDataStream对于类的存储,是将复杂的类分割为很多基本单元实现的。
结合QIODevice,QDataStream可以很方便地对文件、网络套接字等进行读写操作。我们从代码开始看起:
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << QString("the answer is");
out << (qint32)42;
如果你直接运行这段代码,你会得到一个空白的 file.dat,并没有写入任何数据。这是因为我们的file没有正常关闭。为性能起见,数据只有在文件关闭时才会真正写入。因此,我们必须在最后添加一行代码:
file.close(); // 如果不想关闭文件,可以使用 file.flush();
接下来我们将存储到文件中的答案取出来
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);
QString str;
qint32 a;
in >> str >> a;
唯一需要注意的是,你必须按照写入的顺序,将数据读取出来。顺序颠倒的话,程序行为是不确定的,严重时会直接造成程序崩溃。
那么,既然QIODevice提供了read()、readLine()之类的函数,为什么还要有QDataStream呢?QDataStream同QIODevice有什么区别?区别在于,QDataStream提供流的形式,性能上一般比直接调用原始 API 更好一些。我们通过下面一段代码看看什么是流的形式:
QFile file("file.dat");
file.open(QIODevice::ReadWrite);
QDataStream stream(&file);
QString str = "the answer is 42";
stream << str;
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#define cout qDebug() << "[" << __FILE__ <<":" << __LINE__ << "]"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
writeData();
readData();
}
Widget::~Widget()
{
delete ui;
}
void Widget::writeData()
{
//创建文件对象
QFile file("../test.txt");
//打开文件, 只写方式打开
bool isOk = file.open(QIODevice::WriteOnly);
if(true == isOk)
{
//创建数据流,和file文件关联
//往数据流中写数据,相当于往文件里写数据
QDataStream stream(&file);
stream << QString("主要看气质") << 250;
file.close();
}
}
void Widget::readData()
{
//创建文件对象
QFile file("../test.txt");
//打开文件, 只读方式打开
bool isOk = file.open(QIODevice::ReadOnly);
if(true == isOk)
{
//创建数据流,和file文件关联
//往数据流中读数据,相当于往文件里读数据
QDataStream stream(&file);
//读的时候,按写的顺序取数据
QString str;
int a;
stream >> str >> a;
//qDebug() << str.toUtf8().data() << a;
cout << str.toUtf8().data() << a;
file.close();
}
}
上一节我们介绍了有关二进制文件的读写。二进制文件比较小巧,却不是人可读的格式。而文本文件是一种人可读的文件。为了操作这种文件,我们需要使用QTextStream类。QTextStream和QDataStream的使用类似,只不过它是操作纯文本文件的。
QTextStream会自动将 Unicode 编码同操作系统的编码进行转换,这一操作对开发人员是透明的。它也会将换行符进行转换,同样不需要自己处理。QTextStream使用 16 位的QChar作为基础的数据存储单位,同样,它也支持 C++ 标准类型,如 int 等。实际上,这是将这种标准类型与字符串进行了相互转换。
QTextStream同QDataStream的使用基本一致,例如下面的代码将把"The answer is 42"写入到 file.txt 文件中:
QFile data("file.txt");
if (data.open(QFile::WriteOnly | QIODevice::Truncate))
{
QTextStream out(&data);
out << "The answer is " << 42;
}
这里,我们在open()函数中增加了QIODevice::Truncate打开方式。我们可以从下表中看到这些打开方式的区别:
枚举值 描述
新增加的内容将被追加到文件末尾
数据全部清除,游标设置在文件开头。
将行结束符转换成本地格式,例如 Win32 平台
上是 \r\n
我们在这里使用了QFile::WriteOnly | QIODevice::Truncate,也就是以只写并且覆盖已有内容的形式操作文件。注意,QIODevice::Truncate会直接将文件内容清空。
虽然QTextStream的写入内容与QDataStream一致,但是读取时却会有些困难:
QFile data("file.txt");
if (data.open(QFile::ReadOnly))
{
QTextStream in(&data);
QString str;
int ans = 0;
in >> str >> ans;
}
在使用QDataStream的时候,这样的代码很方便,但是使用了QTextStream时却有所不同:读出的时候,str 里面将是 The answer is 42,ans 是 0。这是因为当使用QDataStream写入的时候,实际上会在要写入的内容前面,额外添加一个这段内容的长度值。而以文本形式写入数据,是没有数据之间的分隔的。因此,使用文本文件时,很少会将其分割开来读取,而是使用诸如使用:
这种函数之后再对获得的QString对象进行处理。
默认情况下,QTextStream的编码格式是 Unicode,如果我们需要使用另外的编码,可以使用:
stream.setCodec("UTF-8");
这样的函数进行设置。
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#define cout qDebug() << "[" << __FILE__ <<":" << __LINE__ << "]"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
writeData();
readData();
}
Widget::~Widget()
{
delete ui;
}
void Widget::writeData()
{
QFile file;
file.setFileName("../demo.txt");
bool isOk = file.open(QIODevice::WriteOnly);
if(true == isOk)
{
QTextStream stream(&file);
//指定编码
stream.setCodec("UTF-8");
stream << QString("主要看气质") << 250;
file.close();
}
}
void Widget::readData()
{
QFile file;
file.setFileName("../demo.txt");
bool isOk = file.open(QIODevice::ReadOnly);
if(true == isOk)
{
QTextStream stream(&file);
//指定编码
stream.setCodec("UTF-8");
QString str;
int a;
stream >> str >> a;
cout << str.toUtf8().data() << a;
file.close();
}
}
void Widget::on_pushButton_clicked()
{
QString path = QFileDialog::getOpenFileName(this,
"open", "../" );
if(false == path.isEmpty())
{
QFile file;
file.setFileName(path);
bool isOk = file.open(QIODevice::ReadOnly);
if(true == isOk)
{
QTextStream stream(&file);
//指定编码
stream.setCodec("UTF-8");
QString str = stream.readAll();
ui->textEdit->setText(str);
file.close();
}
}
}
#include "widget.h"
#include "ui_widget.h"
#include //内存文件
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
QByteArray array;
QBuffer memFile(&array); //创建内存文件
memFile.open(QIODevice::WriteOnly);
memFile.write("11111111111111111");
memFile.write("22222222222222222222");
memFile.close();
qDebug() << memFile.buffer();
qDebug() << "array" << array;
QBuffer memFile1;
memFile1.open(QIODevice::WriteOnly);
QDataStream stream(&memFile1);
stream << QString("测试") << 250;
memFile1.close();
qDebug() << memFile1.buffer();
memFile1.open(QIODevice::ReadOnly);
QDataStream in;
in.setDevice(&memFile1);
QString str;
int a;
in >> str >> a;
memFile1.close();
qDebug() << str.toUtf8().data() << a;
}
Widget::~Widget()
{
delete ui;
}