12. 文件操作¶
在Linux中有一句话叫 一切皆文件 。
Linux 中所有内容都是以文件的形式保存和管理的: 普通文件(比如txt文本文件,pdf文件,PNG/JPG,MP3/MP4等等)是文件, 目录(文件夹)是文件, 硬件设备(键盘、监视器、硬盘、打印机)是文件, 套接字(socket)、管道(pipe)等资源也都是文件。
在Qt中提供了能够使用文件相关的类,特定的方法来读写操作,首先我们来看看文件相关的知识。
12.1. 文件¶
所谓文件,我们可以理解为存储在磁盘上的数据,这些数据在计算机中表示为01序列,也就是二进制。 为了方便计算机里的运算、书写以及统计,2、4、8、16、32、64这类进制数也就应运而生。 我们将四个位的二进制数组合在一起就是一个十六进制,0-F 分别表示 0000-1111。
不管是01序列还是十六进制对于我们人而言,要想识别一连串进制数的含义都比较困难, 于是人就凭借自己的智慧想到了用特定的进制数来表示一个字符,比如规定 00110000 来表示字符 0 01000001 来表示字符 A ,这就是ASCII码的产生,也逐渐形成了一个约定——字节(即8个二进制位)。
下面为ASCII码对照表,图片来自网上。
将这些特定的进制数组合就能表达出更丰富的含义,同时也很容易被识别,从一串二进制数到可识别的字符,我们称为字符编码。
在人类的发展历程上,产生了非常多的具有特定含义的字符,比如中文的汉字,甲骨文。 而在上面的ASCII码对照表中没有将一串二进制数识别为汉字的标准,于是就产生了GB2312,BIG5等等中文字符编码和字符集。 同样其他国家也产生了相应的标准,随着计算机发展、网络连接,各个国家都接入了遍布全球的互联网,不同的标准就会造成混乱,比如乱码。 于是聪明的人们又想到用同一套字符编码和字符集来解决这个问题,于是就有了UTF-8和Unicode。
有了字符编码,计算机中的数据就很容易解析和识别,这并不意味着计算机就直接存储着我们能够识别字符,而依然是01序列。 因此我们在读取文件时最开始得到的数据就是01序列,在读取之后再对01序列进行解码、解析。为了方便解析,我们直接通过文件类型来区分文件。 比如txt后缀的文件通常就是以某种文字编码的文本,MP3、JPG等就是符合特定二进制编码的文件, 修改文件后缀名并不会直接改变文件内部数据和格式,而仅仅只是指明当前操作系统对文件的操作方式。
类Unix操作系统,如Linux就是将设备当作特殊文件来处理的,普通文件和设备文件都是由字节序列而构成的信息载体。
12.2. 输入输出类¶
12.2.1. QIODevice¶
QIODevice 是一个抽象类(不能实例化),它为支持读写数据块的设备提供了通用的实现和规范的抽象接口,是所有输出输出类设备(input/output device)的基础类。 其他用到IO设备的类如QFile、QBuffer、QTcpSocket,都是从这个类继承而来。
访问IO设备,需要先调用open()来设置正确的 OpenMode (例如ReadOnly或ReadWrite),一些模式说明,如下表:
模式 |
值 |
描述 |
---|---|---|
QIODevice::NotOpen |
0x0000 |
设备没有打开 |
QIODevice::ReadOnly |
0x0001 |
只读模式打开设备 |
QIODevice::WriteOnly |
0x0002 |
只写模式打开设备,默认覆盖内容 |
QIODevice::ReadWrite |
ReadOnly | WriteOnly |
读写模式打开设备 |
QIODevice::Append |
0x0004 |
追加模式打开设备,数据将会写入设备末尾 |
QIODevice::Truncate |
0x0008 |
覆盖模式打开设备,数据将会从设备开头覆盖写入,原有数据全部清除 |
QIODevice::Text |
0x0010 |
文本模式打开设备 |
打开设备后后,使用write()或putChar()写入数据到文件和设备,并通过调用read(),readLine()或readAll()进行读取;使用完设备后,还需调用close()。
QIODevice区分两种类型的设备:随机访问设备(Random-access devices)和顺序设备(Sequential devices):
随机访问设备,支持使用 seek() 搜索任意位置,文件中的当前位置可通过调用pos()获得,例如QFile和QBuffer就是随机访问设备;
顺序设备,不支持查询任意位置,必须一次性读取数据,例如QTcpSocket和QProcess就是顺序设备。
可以使用 isSequential() 函数来判断设备的类型。
QIODevice在使用的时候,当有新数据可读取时,QIODevice发出readyRead()信号,调用bytesAvailable()查看当前能读取的字节数; 每次将有效负载数据写入设备时,QIODevice都会发出bytesWritten()信号,调用bytesToWrite()来查看待写入的字节数。
QIODevice的相关类继承关系:
12.3. 文件操作相关类¶
在Qt中提供了与文件操作相关的类,通过这些类我们可以获取文件信息,修改文件名称,删除,复制等操作。
12.3.1. QFile¶
QFile 是用于文件的创建、读写、复制、删除等等,可以实现文本文件和二进制文件的读写。
QFile的接口函数可以参考下Qt帮助文档,下面程序是读取普通文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //创建 QFile 对象,同时指定要操作的文件
QFile file("~/test.txt");
//打开文件,以只读方式,并且读取文件时行尾结束符转换成'\n',写入时会将行尾结束符转换成本地格式(Unix 系统中是 "\n",Windows 系统中是 "\r\n")
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
//向文件中写入
file.write("野火嵌入式Qt应用开发实战\n");
file.write("https://doc.embedfire.com/linux/rk356x/Qt/zh/latest/index.html");
//关闭文件
file.close();
//重新打开文件,对文件进行读操作
if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
return;
while (!file.atEnd()) {
QByteArray line = file.readLine();
process_line(line); //处理获取到的一行字符串
}
//关闭文件
file.close();
|
使用QFile类读写文件,只有一些基础的文件读写函数,使用不是很方便, Qt还提供了另外两个文件流操作类 QTextStream 和 QDataStream , 前者用来读写文件文件,后者用来读写二进制文件,QFile可以和它们搭配使用,从整体上提高读写文件的开发效率。
12.3.2. QTextSteam¶
QTextStream 类提供了用于读取和写入文本的便捷接口。 QTextStream可以操作QIODevice, QByteArray 和 QString,调用QTextStream的流操作可以方便的读取文字、行、数字等,还提供了文本填充和对齐的相关格式。
QTextStream内部使用基于Unicode的缓冲区,并且QTextStream使用 QTextCodec 自动支持不同的字符集。 默认情况下,QTextCodec::codecForLocale()用于读取和写入,但是您也可以通过调用setCodec()来设置编解码器。
QTextStream还支持自动Unicode检测,启用此功能(默认行为)后,QTextStream将检测UTF-16或UTF-32 BOM(Byte Order Mark,字节顺序标记),并在读取时切换到适当的UTF编解码器。 QTextStream默认情况下不编写BOM,但是您可以通过调用setGenerateByteOrderMark(true)来启用它,当QTextStream直接在QString上运行时,将禁用编解码器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 用文本流写入指定文件
void MainWindow::on_btn_QTextStream_write_clicked()
{
QFile file(m_textstreampath);
if (!file.open(QIODevice::WriteOnly|QIODevice::Text))
return;
ui->lineEdit->setText(m_textstreampath);
QTextStream out(&file); //用文本流读取文件内容
out << ui->textEdit->toPlainText();
file.close();
}
// 用文本流读取指定文件内容
void MainWindow::on_btn_QTextStream_read_clicked()
{
QFile file(m_textstreampath);
if (!file.open(QFile::ReadOnly|QIODevice::Text))
return;
QTextStream in(&file); //用文本流读取文件内容
ui->lineEdit->setText(m_textstreampath);
ui->textEdit->clear();
QString str = in.readAll(); //读取全部内容
ui->textEdit->append(str); //显示内容
file.close();
}
void MainWindow::on_cBox_Codec_currentIndexChanged(const QString &arg1)
{
QTextCodec::setCodecForLocale(QTextCodec::codecForName(arg1.toLocal8Bit()));
}
|
12.3.3. QDataSteam¶
QDataStream 类用于对数据进行二进制格式的读/写操作。 QDataStream可以序列化C++的基本数据类型的功能,也就是可以将数据按照某种特定的二进制编码进行解析或存储,另外也可以读取/写入未编码的原始二进制数据。
QDataStream 以数据流的方式读写文件,使用Qt预定义的编码方式或者是原始二进制的数据方式, 下面读取和写入文件,先使用QFile这种普通读写,然后转成数据流或者字节流读取,这样便于操作,而且不会乱码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | void MainWindow::on_btn_QDataStream_write_clicked()
{
static int writeCount=0;
QFile file(m_datastreampath);
ui->lineEdit->setText(m_datastreampath);
if(file.open(QIODevice::WriteOnly))
{
QDataStream out(&file);
out << ui->textEdit->toPlainText();
out << ++writeCount;
file.close();
}
}
void MainWindow::on_btn_QDataStream_read_clicked()
{
QFile file(m_datastreampath);
ui->lineEdit->setText(m_datastreampath);
if(file.open(QIODevice::ReadOnly))
{
QDataStream in(&file);
QString str;
int writeCount;
in >> str >> writeCount;
file.close();
ui->textEdit->clear();
ui->textEdit->append(str);
ui->textEdit->append(QString::number(writeCount));
}
}
|
12.3.3.1. 文件夹相关类¶
上面我们了解到了文件的概念,计算机种存储着大量的文件和数据,为了方便管理就引入了文件系统。 文件系统实现对磁盘空间的统一管理,一方面文件系统对磁盘空间进行统一规划,另外一方面文件系统提供给普通用户人性化的接口,我们通过文件系统就能够对文件进行增删改查。
文件系统对文件的管理通常是利用文件夹来实现的, 简单点说文件夹就是一种特殊的文件,内容就是文件列表和文件的索引信息。
12.3.4. QDir¶
QDir 用于操纵路径名,访问有关路径和文件的信息以及操纵底层文件系统。
创建文件夹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | void MainWindow::on_btn_creatdir_clicked()
{
QString curPath=QDir::currentPath();//获取应用程序的路径
QString dlgTitle="选择文件夹"; //对话框标题
QString dirpath=QFileDialog::getExistingDirectory(this,dlgTitle,curPath);
if (dirpath.isEmpty())
return;
dlgTitle="新建文件夹";
QString str="新建文件夹";
QString defaultstr="NEWDIR";
QLineEdit::EchoMode echoMode=QLineEdit::Normal;
bool choose=false;
QString dirinput = QInputDialog::getText(this, dlgTitle,str, echoMode,defaultstr, &choose);
if (choose)
{
if(dirinput.isEmpty())
return;
}
QDir *dir = new QDir;
if(!dir->exists(dirpath+"/"+dirinput))
{
dir->mkdir(dirpath+"/"+dirinput);
}
}
|
遍历文件夹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void MainWindow::on_btn_dirinfo_clicked()
{
QString curPath=QDir::currentPath();//获取应用程序的路径
QString dlgTitle="选择文件夹"; //对话框标题
QString dirpath=QFileDialog::getExistingDirectory(this,dlgTitle,curPath);
if (dirpath.isEmpty())
return;
QDir dir(dirpath);
QStringList files = dir.entryList();
ui->textEdit->clear();
foreach(QString path,files)
{
QFileInfo info(path);
if(!info.baseName().isEmpty())
{
ui->textEdit->append("文件名:"+info.baseName()+" "+"绝对路径:"+info.absoluteFilePath());
}
}
}
|
12.3.5. QFileInfo¶
QFileInfo类提供与系统无关的文件信息, 获取关文件在文件系统中的名称和位置(路径),以及文件的权限和文件类型,文件的大小和上次修改/读取的时间也通过接口获取。 QFileInfo可以指向具有相对或绝对文件路径的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void MainWindow::on_btn_QFileInfo_file_clicked()
{
QString curPath=QDir::currentPath();//获取应用程序的路径
QString dlgTitle="选择文件"; //对话框标题
QString filter="文本文件(*.txt);;图片文件(*.jpg *.gif *.png);;所有文件(*.*)"; //文件过滤器
QString filename=QFileDialog::getOpenFileName(this,dlgTitle,curPath,filter);
if (filename.isEmpty())
return;
QFileInfo fileinfo(filename);
if(!fileinfo.exists())
return;
if(fileinfo.isSymLink())
{
fileinfo.setFile(fileinfo.symLinkTarget());
}
ui->textEdit->clear();
ui->textEdit->append("文件名:"+fileinfo.baseName()+" "+"文件格式:"+fileinfo.completeSuffix()+" "+"文件大小:"+QString::number(fileinfo.size()));
ui->textEdit->append("绝对路径:"+fileinfo.absoluteFilePath());
ui->textEdit->append("文件所有者:"+fileinfo.owner()+" "+"所有者ID:"+QString::number(fileinfo.ownerId())+" "+"文件所属组:"+fileinfo.group()+" "+"所属组ID:"+QString::number(fileinfo.groupId()));
ui->textEdit->append("文件权限:"+QString::number(fileinfo.permissions()));
ui->textEdit->append("文件创建时间:"+fileinfo.birthTime().toString("yyyy-MM-dd hh:mm:ss"));
ui->textEdit->append("上次修改时间:"+fileinfo.lastModified().toString("yyyy-MM-dd hh:mm:ss"));
ui->textEdit->append("上次读取时间:"+fileinfo.lastRead().toString("yyyy-MM-dd hh:mm:ss"));
ui->textEdit->append("元数据更时间:"+fileinfo.metadataChangeTime().toString("yyyy-MM-dd hh:mm:ss"));
}
|
12.3.6. QFileSystemWatcher¶
QFileSystemWatcher通过观察指定路径的列表来监视文件系统中文件和目录的更改, 当文件已被修改,重命名或从磁盘中删除就会发送 fileChanged()。 类似地,当目录或其内容被修改或删除时,会发出directoryChanged()信号。
需要注意的是,一旦文件被重命名或从硬盘删除或者是目录从磁盘上删除,QFileSystemWatcher将停止监控。 QFileSystemWatcher的使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void MainWindow::on_btn_Watch_clicked()
{
QString curPath=QDir::currentPath();//获取应用程序的路径
QString dlgTitle="选择文件夹"; //对话框标题
QString dirname=QFileDialog::getExistingDirectory(this,dlgTitle,curPath);
if (dirname.isEmpty())
return;
QFileSystemWatcher *fileSystemWatcher = new QFileSystemWatcher(this);
fileSystemWatcher->addPath(dirname);
connect(fileSystemWatcher,SIGNAL(directoryChanged(const QString &)),this, SLOT(handDirChanged(const QString &)));
connect(fileSystemWatcher,SIGNAL(fileChanged(const QString &)),this, SLOT(handFileChanged(const QString &)));
}
void MainWindow::handFileChanged(const QString &path)
{
QString dlgTitle="information";
QString str="文件改变:"+path;
QMessageBox::information(this, dlgTitle, str,QMessageBox::Yes);
}
void MainWindow::handDirChanged(const QString &path)
{
QString dlgTitle="information";
QString str="文件夹改变:"+path;
QMessageBox::information(this, dlgTitle, str,QMessageBox::Yes);
}
|
12.3.7. QTemporaryFile和QTemporaryDir¶
QTemporaryFile用于安全地创建唯一的临时文件,该文件本身是通过调用open()创建的。
临时文件的名称保证是唯一的(即,保证您不会覆盖现有文件),并且在销毁QTemporaryFile对象时将删除该文件。
临时文件的打开方式为QIODevice::ReadWrite
这样可避免将数据存储在临时文件中的应用程序破坏数据。
使用QTemporaryFile创建临时文件非常简单,可以使用静态函数QTemporaryFile::createTemplateFile()创建文件,会返回一个QTemporaryFile对象。 可以像普通文件一样进行读写操作,使用open()打开,当不再需要使用临时文件时,调用close() 方法关闭文件即可。
需要注意的是,临时文件在程序结束后会被自动删除(QTemporaryFile对象销毁时),因此不需要手动删除临时文件。 如果需要手动控制临时文件的删除时间,可以使用 QFile::setAutoRemove() 方法设置是否自动删除文件。
示例程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include <QTemporaryFile>
#include <QFile>
// 创建临时文件
QTemporaryFile file;
if (!file.open()) {
qDebug() << "Failed to create temporary file.";
return -1;
}
// 写入数据到临时文件
QFile tempFile(file.fileName());
if (!tempFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Failed to open temporary file for writing.";
return -1;
}
tempFile.write("Test, temporary file", -1);
tempFile.close();
// 读取临时文件中的数据并输出
if (!file.open()) {
qDebug() << "Failed to open temporary file for reading.";
return -1;
}
qDebug() << "Temporary file content:" << file.readAll();
file.close();
// 查看文件信息
QFileInfo info(tempFile);
qDebug() << info.isFile();
qDebug() << info.path();
qDebug() << info.fileName();
|
同样,QTemporaryDir用于安全地创建唯一的临时目录,操作类似,参考下Qt帮助手册
12.3.8. QCoreApplication¶
QCoreApplication 是无UI应用程序提供事件循环的类,是所有应用程序的基类。 对于GUI程序,应该用QGuiApplication,而对于采用了Qt Widget模块的程序,应该使用QApplication,例如:我们使用Qt Creator创建的Qt Widget Application都是基于QApplication。
它们之间的继承关系是:QCoreApplication -> QGuiApplicatioin -> QApplication。
QCoreApplication提供了一些有用的静态函数,可以用来获取应用程序的名称,启动路径等信息。 继承于此类的子类,也可以使用QCoreApplication的相关方法,如applicationDirPath()获取可执行应用文件所在目录;applicationFilePath()获取可执行应用文件路径。
1 2 3 4 | // 默认文件路径和名称
m_datastreampath = qApp->applicationDirPath()+"/datastream.dat";
m_textstreampath = qApp->applicationDirPath()+"/textstream.txt";
m_filepath = qApp->applicationDirPath()+"/document";
|