目录
一、Qt文件系统
1.Qt文件系统的介绍
文件操作是所有开发程序和开发语言必不可少的一个重要的部分,无论是C/C++、还是系统级别操作等提供了一套完整的文件操作接口,Qt作为了一个通用开发库,也必须提供了跨平台的文件操作能力。Qt中提供了很多文件的类,通过这些类能够对文件系统进行快速的操作了。
|------|----------|--------------------|-----------|
| | C语言标准库 | C++标准库 | Linux系统调用 |
| 打开文件 | fopen函数 | fstream类 | open函数 |
| 读取文件 | fread函数 | operator<<成员函数 | read函数 |
| 写入文件 | fwrite函数 | operator>>()成员函数 | write函数 |
| 关闭文件 | fclose函数 | close成员函数 | close函数 |
而且Windows系统下,也会有Windows的一套系统调用接口去进行文件操作,那么不管是不同平台下,还是语言层面都有很多对文件的操作,为什么Qt还要自己去弄一套自己的文件操作呢?因为Qt的诞生节点有关,诞生的时候,C++还有没完整的标准化概念,所以Qt自己弄了一套方案。再编写Qt程序的时候,更推荐Qt自己提供的一套文件操作,因为QString等Qt自己的类和自己的文件操作配合会更方便一些。当然也可以使用上述的其他体系去实现文件操作。
2.Qt文件类
在Qt中的文件操作中核心的无非也就是打开文件、读写文件和关闭文件的操作。Qt提供了一个QFile类,该类就可以实现上述的操作和对文件的管理。OFile类继承自OFileDevice类,该类中就提供了文件交互操作的底层功能。QFileDevice类的父亲是QIODevice,QIODevice的父亲就是所有类的祖先QObject类。

QIODevice类是Qt中所有的输入输出设备的基础类,磁盘文件、网络通信的Socket套接字、串口、蓝牙等等都是属于输入输出型设备,所以都是继承于QIODevice类。
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 类名 | 介绍 |
| QFile | 用于文件操作和文件读写的类 |
| QSaveFile | 用于安全保存文件的类,使用该类对象保存文件的时候,会先将数据写入一个临时文件,成功提交折后才会将数据写入最终的文件当中,如果保存过程中有错误的话,临死文件的内容不会写入最终文件,保证最终文件中不会出现数据残缺或丢失的问题,也就是说如果写入失败也会保留原有的数据。在保存比较大型的文件或者复杂格式的文件可以使用这个类。 |
| QTemporaryFile | 用于创建临时文件的类,可以创建出一个唯一的临时文件,该QTemporaryFile对象销毁的时候,临时文件也就销毁了 |
| QTcpSocket、QUdpSocket | 实现了TCP和UDP通信的类 |
| QSerialPort | 实现了串口通信的类,通过该类可以实现计算机与串口设备的通信。串口是一个比较老的通信方式,一般是在嵌入式系统上用的较多。 |
| QBluetoochSocket | 用于蓝牙通信的类 |
| QPrecess | 用于启动外部程序的类,可以给程序传递参数内容。相当于fork、exec操作的封装。 |
| QBuffer | 是以一个QByteArray对象作为缓冲区,对这个缓冲区进行I/O操作的类。相当于一个缓冲区类 |
二、Qt文件的操作
1.文件的打开
QFile::QFile(const QString &name); //创建QFile对象
virtual bool open(QIODevice::OpenMode mode) override; //打开文件
Qt中打开文件之前是需要创建一个QFile对象的并传递一个文件路径,之后配合着QFile类内部的open函数进行打开文件。open打开文件的函数传递的参数是文件的打开方式,也就是读方式、写方式、追加写等等方式,不用传递文件路径了,因为QFile对象内部已经记录了管理的文件所在的路径了。OpenMode是一个枚举类型,枚举出了一些打开文件的方式。

QFile类中也提供了其他的open函数,但是没有上述的方便。需要传递C语言标准库中的文件对象FILE或者底层的文件描述符。
bool open(FILE *fh, QIODevice::OpenMode mode,
QFileDevice::FileHandleFlags handleFlags = DontCloseHandle);bool open(int fd, QIODevice::OpenMode mode,
QFileDevice::FileHandleFlags handleFlags = DontCloseHandle);
2.文件的读写操作
读取操作函数
// 尽可能的读取文件内容
qint64 read(char* data, qint64 maxSize);
QByteArray read(qint64 maxSize);
// 一次性将文件内容都读取出来
QByteArray readAll();
// 读取一行数据
qin64 readLine(char* data, qint64 maxSize);
QByteArray readLine(qint64 maxSize = 0);
QByteArray返回值是一个字符数组,是Qt对于字符数组的封装,QString类中重载了operator =函数可以传递该QByteArray参数,所以很容易就可以转化为QString类型的字符串了。但是前提是打开的文件一个文本文件,如果是二进制文件的话,就会出现一些问题了。
写入操作函数
qint64 write(const char* data, qint64 maxSize);
qint64 write(const char* data);
qint64 write(const QByteArray& byteArray);
3.关闭操作
virtual void close();
一定要在操作文件之后关闭文件,从底层来说,系统的文件描述符表有上限,一直运行的程序如果不关闭的话,文件描述符越开越多,就会占满文件描述符表了,就会出问题。而且每一个文件描述符都会关联一个strutc file,系统也要将这些struct file结构体维护起来。还有就是如果不关闭的话,文件就会一直处于打开状态,在内存中,系统还要维护这些打开的文件。所以说文件操作完毕之后一定要关闭文件。
4.接口使用案例
案例:实现打开文件和保存文件的操作。
该案例是模拟了一个记事本的操作,在菜单栏中提供了打开文件和保存文件的操作,打开文件关联的槽函数是,将指定路径文件的内容读取出来放入到多行输入文本框中,保存文件关联的槽函数是,将文本框中的内容写入到指定路径的文件当中。
在保存文件的时候,需要QStrin的字符串文本类型重写转化为QByteArray类型,Qt中也提供了很多的转换函数。这里我们发现会有很多种转换的函数,而且需要我们自己手动转化,这是为什么呢?QByteArray存放的是我们打开的文件,存储的内容原始字节表示形式。而QString字符串是根据特定的编码格式将原始字节数据转化为了特定的文本格式了。
当QByteArray转化为QStirng的时候,默认是使用utf8编码方式进行转化。当然如果我们想要 转化为其他形式的话,也是需要使用Qt内置的一些接口进行转换的。当然前提是该文本类型不是二进制类型,否则二进制类型文件转化后会成一堆乱码。在打开文件的时候,一般都是打开的文本文件,所以默认使用的utf8的转换方式。而QString转化为QByteArray的时候,系统不知道我们想要保存的文件是什么类型,而且存放二进制文件和文本文件的几率基本上是一样的。所以说没有设置默认的存储类型,而是需要我们自己去设置。
QString转换QByteArray函数

cpp
const QString text = edit->toPlainText();
file.write(text.toUtf8());
QByteArray转换QString函数
cpp
QByteArray latin1Array = "Hello, World";
QString str2 = QString::fromLatin1(latin1Array);
QByteArray local8BitArray = "Hello, 世界";
QString str3 = QString::fromLocal8Bit(local8BitArray);
案例代码
cpp
//edit是一个QTextEdit类型的成员函数,定义在mainwindow.h文件当中
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//修改窗口标题
this->setWindowTitle("记事本");
//创建菜单栏
QMenuBar* menubar = this->menuBar();
this->setMenuBar(menubar);
//添加菜单
QMenu* menu = new QMenu("文件");
menubar->addMenu(menu);
//添加菜单项
QAction* action1 = new QAction("打开文件");
QAction* action2 = new QAction("保存文件");
menu->addAction(action1);
menu->addAction(action2);
//创建一个输入框
edit = new QTextEdit();
//设置输入框的字体
QFont font;
font.setPixelSize(20);
edit->setFont(font);
//将窗口的中心部件设置为多行输入框
this->setCentralWidget(edit);
//将菜单项绑定槽函数
connect(action1, &QAction::triggered, this, &MainWindow::openfile);
connect(action2, &QAction::triggered, this, &MainWindow::savefile);
}
MainWindow::~MainWindow()
{
delete ui;
}
//打开文件
void MainWindow::openfile()
{
// 首先弹出一个选择文件的对话框
QString path = QFileDialog::getOpenFileName(this);
// 把选择的文件名显示到状态栏里面
QStatusBar* statusbar = this->statusBar();
statusbar->showMessage(path);
//根据用户选择的路径,打开文件
QFile file(path);
bool ret = file.open(QIODevice::ReadOnly);
if(ret == false)
{
statusbar->showMessage(path + "打开失败");
return;
}
//读取文件写入输入框
QString text = file.readAll();
//关闭文件
file.close();
//把读取的文件内容写入到输入框中。
edit->setText(text);
}
//保存文件
void MainWindow::savefile()
{
//弹出一个保存文件对话框,选择保存的路径
QString path = QFileDialog::getSaveFileName(this);
//在状态栏中显示文件名
QStatusBar* statusbar = this->statusBar();
statusbar->showMessage(path);
//根据选择的路径构造一个QFile对象,并打开文件
QFile file(path);
bool ret = file.open(QIODevice::WriteOnly);
if(ret == false)
{
statusbar->showMessage("保存失败");
return;
}
//获取输入框的内容
const QString text = edit->toPlainText();
//写入到文件当中
file.write(text.toUtf8());
//关闭文件
file.close();
}

5.获取文件的相关属性
在Qt当中FileInfo类可以获取到Qt文件的相关属性。对于该操作在C/C++标准库中先前是没有相关此操作的,如果我们想要获得文件的属性就需要进系统调用进行获取,直到C++17才引入了相关的模块,提供了此功能。所以Qt就自己设计了一套获取文件相关属性的操作。
使用案例
cpp
void Widget::on_pushButton_clicked()
{
//弹出对话框,选择文件并获取文件信息
QString path = QFileDialog::getOpenFileName(this);
//构造QFileInfo对象
QFileInfo fileinfo(path);
ui->textEdit->append("文件名: " + fileinfo.fileName());
ui->textEdit->append("文件后缀: " + fileinfo.suffix());
ui->textEdit->append("文件路径: " + fileinfo.path());
ui->textEdit->append("文件大小: " + QString::number(fileinfo.size()));
ui->textEdit->append("文件类型是否是普通文件: " + QString(fileinfo.isFile() == true ? "是" : "不是"));
ui->textEdit->append("文件类似是否是一个目录: " + QString(fileinfo.isDir() == true ? "是" : "不是"));
}
更多的QFileInfo内置的函数以及使用可以查看帮助文档。
三、文件的分类
1.文本文件
文本文件是由字符序列组成的文件内容,通常包含的是人类可以看懂的文本内容。这些字符按照一定的编码方式进行存储(UTF8、ASCII等),每个字符占用一个或者多个字节。其实这些字符底层也一定是一堆二进制数字,但是通过特定的编码,每个字符都会对应一个特定的二进制序列,会将这些二进制数据和我们能看懂的字符进行相互转化。
文本文件常用于存储配置信息、源代码、日志文件、CSV 文件等。例如,配置文件.ini通常存储键值对,如key=value的形式;CSV 文件存储表格数据,以逗号分隔不同的字段。
2.二进制文件
二进制文件存储的就是原始的二进制数据了,不是字符序列,大多数都是一些图像、视频和音频数据、数据库文件以及可执行文件等内容。这些数据不是并没有经过特定的编码格式排列,所以说并非是我们可以看懂的。通过一些特定的编码方式进行转化的话,转化后的也是一些乱码的字符数据。
3.二者的区别
- 可读性:文本文件是给人类读的,所以可读性一定是很好的,而二进制文件的话,通常是给计算机看的,计算机会解析成一些图片、音频或者执行一些程序。
- 存储效率:存储效率来说的话,文本文件的存储时,需要根据使用的编码方式进行特定格式的存储,而且一个文字可能会占用多个字节,例如UTF8中一个英文字符占用一个字节,而对于一些汉字来说要占用多个。而二进制文件的存储来说,不会进行字符编码,直接存储原始数据即可,存储效率比较高。对于一些需要存储大量数据的场景,如存储音频样本或图像像素,使用二进制文件可以更紧凑地存储数据,避免了字符编码的额外开销。但也并非绝对的,例如存储一个数字900,对于文本文件来说就是3个字符是3个字节,而对于二进制来说就是一个int类型的数字,需要4个字节了。
- 跨平台性:文本文件在不同的操作系统下,换行符是不同的,在Windows上是\r\n,而linux下确实\n,所以可能会导致在跨平台处理文本文件的时候,需要进行换行字符的转化。而二进制文件来说虽然不受字符编码和换行符的影响,但是不同平台下可能对于数据的存储个数有不同的处理方式,例如大小端模式的区别。不同平台存储多字节数据的字节顺序可能不同,因此在跨平台使用二进制文件时,可能需要进行字节序的转换或其他数据格式的调整。