9.2 读写二进制文件
9.2 读写二进制文件
在9.1节我们讲完了文本文件的读写,实际上在很多类型的文件中,二进制文件才是种类繁多的。而Qt中也提供了对二进制文件的操作的类,这个类叫做QDataStream,这个类提供了一些用于操作二进制文件的成员方法。我们接下来将通过一个例子来讲解,如何实现自定义的数据的串行化(也就是说将数据项分为一条一条的写,如何分为一条一条的去读)。
案例:通过读写学生的信息的文件
(1)新建一个Qt的控制台应用程序,项目名称就为DataStreamTest
(2)新建C++类,名称为Student,用来编写一个学生类,然后写以下内容
student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <QString>
#include <QDateTime>
#include <QDebug>
class Student
{
public:
Student();
Student(QString _id,QString _name,QDateTime _createDate,double _chineseScore = 0,double _mathScore = 0,double _englishScore = 0);
~Student();
public:
const QString&ID();
const QString &Name();
const QDateTime&CreateTime();
const double&ChineseScore();
const double&MathScore();
const double&EnglishScore();
public:
void ShowStudent()const;
private:
QString m_id;
QString m_name;
QDateTime m_createDate;
double m_chineseScore;
double m_mathScore;
double m_englishScore;
};
#endif // STUDENT_H
student.cpp
#include "student.h"
Student::Student()
{
}
Student::Student(QString _id,QString _name,QDateTime _createDate,double _chineseScore,double _mathScore,double _englishScore)
{
m_id = _id;
m_name = _name;
m_createDate = _createDate;
m_chineseScore = _chineseScore;
m_mathScore = _mathScore;
m_englishScore = _englishScore;
}
Student::~Student()
{
}
const QString &Student::ID()
{
return m_id;
}
const QString &Student::Name()
{
return m_name;
}
const QDateTime &Student::CreateTime()
{
return m_createDate;
}
const double &Student::ChineseScore()
{
return m_chineseScore;
}
const double &Student::MathScore()
{
return m_mathScore;
}
const double &Student::EnglishScore()
{
return m_englishScore;
}
void Student::ShowStudent() const
{
qDebug() << m_id << "-" << m_name << "-" << m_createDate << "-" << m_chineseScore << "-" << m_mathScore << "-" << m_englishScore;
}
(3)在main.cpp中写以下内容
#include <QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDateTime>
#include "student.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//1.先是写数据
Student stu1("20310120711","蜡笔小新Pointer",QDateTime::currentDateTime(),88.75,65.73,98.23);
QFile dataFile("./binary.dat");
if(dataFile.open(QIODevice::ReadWrite|QIODevice::Truncate))
{
QDataStream dataStream(&dataFile);
dataStream << stu1.ID() << stu1.Name() << stu1.CreateTime() << stu1.ChineseScore() << stu1.MathScore() << stu1.EnglishScore();
}
else
{
qDebug() << dataFile.errorString();
}
dataFile.close();
//2.然后是读数据
QFile file("./binary.dat");
if(file.open(QIODevice::ReadOnly))
{
QDataStream stream(&file);
QString id,name;
QDateTime createDate;
double chinese,math,english;
operator>>(stream,id);//这里我们知道,Qt对于这种QString这类数据类型,采用的运算符重载是全局的方式重载的
operator>>(stream,name);
operator>>(stream,createDate);
stream.operator>>(chinese).operator>>(math).operator>>(english);//基础数据类型则是使用成员方式重载的
Student buf(id,name,createDate,chinese,math,english);
buf.ShowStudent();
}
else
{
qDebug() << file.errorString();
}
file.close();
return a.exec();
}
(4)运行

我们会发现,对于二进制文件,我们只能在程序中按照实际写入的字段的顺序去读。而直接将二进制文件当做文本文件去打开,里面的内容是看不明白。实际上对于二进制文件,还需要注意在写入时的字节序的问题。这个我们接下来会提到。
9.2.1 QDataStream的一些其他注意事项和成员方法
在Qt中有很多类型都可以写入到二进制文件中,比如QBrush、QColor、QDateTime、QFont、QPixmap、QString、QVariant等都可以写入到文件中。但是始终要记住,写入的数据是按照什么顺序写入的,那么就必须要按照什么样的类型读出。不然的话就会导致输出内容时出问题。
在将QVector,QList,QSet,QMap这些数据写入到数据流中时,,需要调用全局重载的operator>>或operator<<来从流中读取数据到对应的容器的对象中或者是写入到流中。这个QDataStream支持将QBrush、QColor、QDateTime、QFont、QPixmap、QString、QVariant、QCursor、QIcon、QImage、以及Qt中的常用的容器都可以写入到数据流对应的IO设备中。以下是一个对QDataStream类的较为详细的使用案例:
#include <QCoreApplication>
#include <QDebug>
#include <QColor>
#include <QDataStream>
#include <QFile>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//1.QDataStream的几个重要的构造器
///QDataStream(const QByteArray &a)//通过QByteArray的常引用来构造一个QDataStram对象,由于IO设备是const的,因此不能对数据流进行写操作(调用operator<<(Type))
///QDataStream(QByteArray *a, QIODevice::OpenMode mode)//通过QByteArray的指针构造QDataStream对象(由于传入的指针既不是常量指针,也不是指针常量,因此可以对数据流进行读和写操作)
///QDataStream(QIODevice *d)//通过基于IO设备指针构造QDataStream对象,也就是说,只要是基于QIODevice的派生类都可以,例如(QFile,QTcpSocket,QUdpSocket,QBuffer,QProcess等)
{
//1.QDataStream(const QByteArray &a)
QFile file("C:/Users/Administrator/Desktop/HDTunePro_Win8_10.exe");
if(file.open(QIODevice::ReadOnly))
{
QByteArray data;//准备数据源
data = file.readAll();//将文件中的信息全部读入数据源中
QDataStream stream(data);//该数据流对象只能在其中做读操作,不能进行写操作
char*buf = new char[data.size()];
stream.readRawData(buf,data.size());
qDebug() << buf;
delete [] buf;
buf = nullptr;
}
else
{
qDebug() << file.errorString();
}
file.close();
//2.QDataStream(QByteArray *a, QIODevice::OpenMode mode)
QFile file2("C:/Users/Administrator/Desktop/晶片战争(Chris Miller).pdf");
if(file2.open(QIODevice::ReadOnly))
{
QByteArray data(file2.readAll());
QDataStream stream(&data,QIODevice::ReadWrite|QIODevice::Truncate);//在这种构造函数中,所构造出的QDataStream可读可写
stream.operator<<(13);
operator<<(stream,QString("Hello,World!"));
qDebug() << data.size();
}
else
{
qDebug() << file2.errorString();
}
file2.close();
//3.QDataStream(QIODevice *d)
QFile file3("C:/Users/Administrator/Desktop/晶片战争(Chris Miller).pdf");
if(file3.open(QIODevice::ReadWrite))
{
QDataStream stream(&file3);//在使用基于QIODevice的指针来作为数据流的IO设备时,需要注意,这里构造器中不需要指定读写方式,因为读写方式取决于IO设备的打开方式
stream.operator<<("Hello");
}
else
{
qDebug() << file3.errorString();
}
file3.close();
}
//2.
//void QDataStream::abortTransaction()
//void QDataStream::startTransaction()
//bool QDataStream::commitTransaction()
//void QDataStream::rollbackTransaction()
//
{
//终止以事务的方式来读写数据流中的IO设备文件
//开始以事务的方式读写数据流中的IO设备文件
//提交事务
//回滚事务
///实际上如果我们开始了以事务的方式读写IO设备,那么我们在使用operator<<之后,我们在平时应用场景中,数据会被立即写入到IO设备中,但如果我们以事务的方式,在提交事务之前,是不会将数据写到设备中的
QFile testFile("./testFile");
if(testFile.open(QIODevice::ReadWrite))
{
QDataStream stream(&testFile);
stream.startTransaction();//开始事务
stream.operator<<(123);//准备写入一个123
stream.operator<<(345);//准备写入一个345
stream.commitTransaction();//提交事务(如果程序被异常终止,那么会导致事务没有被提交,那么就会导致数据没有写到文件中)此时就需要调用rollbackTransaction()去回滚事务,或者是在不需要的情况下调用abortTransaction()终止事务
}
else
{
qDebug() << testFile.errorString();
}
testFile.close();
}
//3.bool atEnd() const
{
qDebug() << "----bool atEnd() const----";
//该函数用于判断是否已经读取到了流末尾的位置
QFile file("./qmap");
file.open(QIODevice::ReadWrite);
QDataStream stream(&file);
QMap<int,QString>m1;
m1.operator[](0) = QString("ABCD");
m1.operator[](1) = QString("EFGH");
operator<<(stream,m1);//这里我们尝试将QMap写入到数据流中
file.close();
//然后我们读取它
QMap<int,QString>m2;
file.open(QIODevice::ReadOnly);
stream.setDevice(&file);
do
{
operator>>(stream,m2);//注意,对于Qt里面定义的一些UDT数据类型,文本流写入时采用的是全局重载的operator>>或operator<<
}while(!stream.atEnd());
qDebug() << m2;
file.close();
qDebug() << "----END bool atEnd() const----";
}
//QDataStream::ByteOrder byteOrder() const
//void setByteOrder(QDataStream::ByteOrder bo)
{
qDebug() << "----QDataStream::ByteOrder byteOrder() const;void setByteOrder(QDataStream::ByteOrder bo)----";
//这两个成员,一个是返回当前流中所用的字节序,一个是手动设置流中的字节序
//这个字节序可以通过QSysInfo中进行获取,这个和处理器和操作系统有关系,除非有特殊必要,否则不要去手动设置字节序,否则在别的设备上读文件时,可能会出问题
QFile file("./qset");
if(file.open(QIODevice::ReadWrite))
{
QSet<QString>s1;
s1.insert("A");
s1.insert("B");
s1.insert("C");
QDataStream stream(&file);
stream.setByteOrder(QDataStream::ByteOrder::LittleEndian);
operator<<(stream,s1);
switch (stream.byteOrder())
{
case QDataStream::ByteOrder::BigEndian:
qDebug() << "BigEndian";
break;
case QDataStream::ByteOrder::LittleEndian:
qDebug() << "LittleEndian";
break;
}
}
else
{
qDebug() << file.errorString();
}
file.close();
qDebug() << "----END QDataStream::ByteOrder byteOrder() const;void setByteOrder(QDataStream::ByteOrder bo)----";
}
//QIODevice *device() const
//void setDevice(QIODevice *d)
{
//该函数会返回所用的数据流中的IO对象
//重新设置IO设备
QFile file("./qset");
if(file.open(QIODevice::ReadOnly))
{
QDataStream stream(&file);
stream.setByteOrder(QDataStream::LittleEndian);
QSet<QString>s1;
operator>>(stream,s1);
qDebug() << s1;
QIODevice*ioDevice = stream.device();
qDebug() << ioDevice->size();
}
else
{
qDebug() << file.errorString();
}
file.close();
}
//QDataStream::FloatingPointPrecision floatingPointPrecision() const
//void setFloatingPointPrecision(QDataStream::FloatingPointPrecision precision)
{
//返回当前流中所存储浮点数的精度(32位浮点还是64位浮点),这是因为有时候浮点数的存储确实会根据实际的需要去设置64位浮点存储还是32位浮点数存储,如果单独设置过,那么在读取的时候也要提前设置好解析浮点数的精度,否则可能会导致丢失精度的问题
//设置浮点数的存储的位数(32或64)
QFile doubleFile("./doubleFile");
if(doubleFile.open(QIODevice::ReadWrite))
{
QDataStream stream(&doubleFile);
stream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::DoublePrecision);
stream.operator<<(3.1415926);
}
else
{
qDebug() << doubleFile.errorString();
}
doubleFile.close();
}
//QDataStream &readBytes(char *&s, uint &l)
//QDataStream &writeBytes(const char *s, uint len)
{
qDebug() << "----QDataStream &readBytes(char *&s, uint &l)----";
//该函数用于从数据流中读取l个字符到缓冲区,但是请注意,这里的缓冲s需要提前用new char[size]分配好,然后最好用memset打扫一下内存,然后这里为什么传入的是uint的引用,因为如果流中没有可读的了,那么这个函数就会将l设置为0,并且s设置为nullptr,这样子我们就可以通过形参带回的返回值得知是不是读取成功了
QByteArray data;
QDataStream stream(&data,QIODevice::ReadWrite);
stream.writeBytes("Hello,World!",13);
char*buf = new char[13];
uint len = 13;
stream.readBytes(buf,len);
qDebug() << buf;
delete [] buf;
qDebug() << "----END QDataStream &readBytes(char *&s, uint &l)----";
}
//int readRawData(char *s, int len)
//int writeRawData(const char *s, int len)
//int skipRawData(int len)
{
qDebug() << "----int readRawData(char *s, int len)----";
//用于读取原始数据,建议使用该函数而不是readbytes,原始数据就是我们的文件在磁盘上的原始的数据流,就是存储在磁盘的扇区中的东西,有兴趣的可以用DiskGenius查看文件的原始RAW,然后自己写程序读取出来看看是不是和DG里面显示的一样
//用于写原始数据
//用于跳过数据流中的前len个字节的数据
QFile PIFile("./PI");
if(PIFile.open(QIODevice::ReadWrite))
{
const double PI = 3.1415926;
QDataStream stream(&PIFile);
stream.writeRawData((char*)&PI,sizeof(double));//写双精度数PI的原始数据
}
else
{
qDebug() << PIFile.errorString();
}
PIFile.close();
PIFile.open(QIODevice::ReadOnly);
QDataStream stream(&PIFile);
double buf(0);
stream.readRawData((char*)&buf,sizeof(double));
qDebug() << "PI=" << buf;
PIFile.close();
qDebug() << "----END int readRawData(char *s, int len)----";
}
//void resetStatus()
//void setStatus(QDataStream::Status status)
//QDataStream::Status status() const
{
//重置数据流的状态
//设置数据流的状态
//返回当前数据流的状态
QList<int>l;
for(int i = 0;i < 1001;++i)
{
l.push_back(i*10);
}
QFile listFile("./listFile");
if(listFile.open(QIODevice::ReadWrite))
{
QDataStream stream(&listFile);
operator<<(stream,l);
switch (stream.status())
{
case QDataStream::Status::Ok:
qDebug() << "QDataStream::Status::Ok";
break;
case QDataStream::Status::ReadPastEnd:
qDebug() << "QDataStream::Status::ReadPastEnd";
break;
case QDataStream::Status::WriteFailed:
qDebug() << "QDataStream::Status::WriteFailed";
break;
case QDataStream::Status::ReadCorruptData:
qDebug() << "QDataStream::Status::ReadCorruptData";
break;
}
}
else
{
qDebug() << listFile.errorString();
}
listFile.close();
}
//void setVersion(int v)
//int version() const
{
//设置数据流的版本(实际上这个版本是数据流的Qt的版本,因为在Qt有很多的版本,但是可能不同版本之间的Qt,可能对QDataStream类的实现有着细微的差别,这从一定程度上可能会导致一些问题.因此引入了版本机制来避免这类问题的发生
QMap<int,QString>m1;
m1[0] = QString("ABCD");
m1[1] = QString("EFGH");
QByteArray data;
QDataStream stream(&data,QIODevice::ReadWrite);
stream.setVersion(QDataStream::Version::Qt_5_10);
operator<<(stream,m1);
qDebug() << data.size();
qDebug() << stream.version();
}
return a.exec();
}
本节代码:https://files.cnblogs.com/files/blogs/792763/DataIO.zip?t=1720117388&download=true

浙公网安备 33010602011771号