读取和写入二进制数据

Qt中载入和保存二进制数据的最简单方式是通过实例化一个 QFile 打开文件,然后通过QDataStream对象存取它。QDataStream 提供了一种与运行平台元关的存储格式,它不仅支持 List<T>和 QMap<K, T>等Qt容器类,还支持整型和双精度型等基本的 C++类,以及其他许多种 Qt数据类型,诸如 QByteArray、QFont、QImage、 QPìxmap、QString、QVariant。

下面是如何在一个名为 facts.dat 的文件中存储一个整型数据、一个 QImage 以及一个 QMap<QString, QColor> 的代码:

QImage image("philip.png");
QMap<QString, QColor> map;
map.insert("red", Qt::red);
map.insert("green", Qt::green);
map.insert("blue", Qt::blue);QFile file("facts.dat");
if(!file.open(QIODevice::WriteOnly))
{   std::cerr << "Cannot open file for writing: "<< qPrintable(file.errorString()) << std::endl;return;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_3);
out << quint32(0x12345678) << image << map;

如果不能打开文件,就通知用户并返回。qPrintable()宏将为 QString返回一个 const char * 。 [另外一种方法是使用 QString::toStdString(),它返回 std::string,<iostream>将为其重载一个<<。]

如果成功打开文件,就创建 QDataStrearn,同时设置它的版本号。版本号是一个整数,它会影响Qt。数据类型的表示方式(基本的 C++ 数据类型的表示方式总是相同的)。

为了确保数字0x12345678 在所有平台上都是按照无符号 32位整数形式写入的,我们将它强制转换为quint32,这是一个严格保证为 32 位的数据类型。为了保证互通性,QDataStream会默认将高字节在后的顺序 (big-endian) 作为标准,这可以通过调用setByteOrder()来改变。

我们不必明确地关闭这个文件,因为当 QFile 变量离开它的作用域时,文件会自动关闭。如果想检验数据是否被真正写人,可以调用flush()并检查其返回值(若返回值为 true ,则表示成功写入数据)。

读回数据的代码借鉴了用以写入数据的代码:

quint32 n;
QImage image;
QMape<QString, QColor> map;QFile file("facts.dat");
if(!file.open(QIODevice::ReadOnly))
{std::cerr << "Cannot open file for reading: "<< qPrintable(file.errorString())<< std::endl;return;
}
QDataStream in(&file);
in.setVersion(QDataStream::4_3);
in >> n >> image >> map;

用于读取数据的 QDataStream 版本与用于写入数据的 QDataStream 版本一样。必须保证这一点。通过手动编写版本号,可以确保应用程序可以正常读写数据(假设它是由 Qt 4_3 或者更新的Qt。版本进行编译的)。

QDataStream这种存储数据的方式使我们可以完全连续地读回数据。例如,QByteArray 表示一个由它们自己字节数尾随的 32 位的宇节计数。利用readRawBytes()和 writeRawBytes(),QDataStream 也可以用来读写一些原始的二进制数据而不需要任何的字节计数首部。

当从 QDataStream 读取数据时,错误处理相当容易。流有一个 status() 值,可以是 QDataStream::Ok、QDataStream::ReadPastEnd或者QDataStream::ReadCorruptData。如果错误发生,则>>操作符总是读取0值或者空值。这表示通常可以简单地读取一个文件而不用担心出错,并在最后检查status()值以确定读取的文件数据有效。

QDataStream 可以处理多种 C++ 和Qt数据类型,还可以通过重载<<和>>操作符为用户的自定义类型增加支持。

以下是可用于 QDataStream 的自定义数据类型的定义:

class Painting
{public:Painting() { myYear = 0; }Painting(const QString &title, const QString &artist, int year){myTile = title;myArtist = artist;myYear = year;}void setTitle(const QString &title) { myTitle = title; }QString title() const { return myTitle; }...
private:QString myTitle;QString myArtist;int myYear;
};
QDataStream &operator<<(QDataStream &out, const Painting &painting);
QDataStream &operator>>(QDataStream &in, Painting &painting);

下面的代码说明了如何实现<<操作符:

QDataStream &operator<<(QDataStream &out, const Painting &painting)
{out << painting.title() << painting.artist()<< quint32(painting.year());return out;
}

为了输出 Painting,我们简单地输出两个 QString和一个quint32 。在函数的最后,返回这个流。这是一个常见的允许以一个输出流来使用一系列<<操作符的 C++ 习惯用法。

out << painting1 << painting2 << painting3;

operator>>()的实现与operator<<()的实现相似:

QDataStream &operator>>(QDataStream &in, Painting &painting)
{QString title;QString artist;quint32 year;in >> title >> artist >> year;painting = Painting(title, artist, year);return in;
}

为自定义数据类型提供流操作符有几个好处。其中之一是允许流输出使用自定义类型的容器类。例如:

QList<Painting> paintings = ...;
out << paintings;

还可以在容器类中轻松读取:

QList<Painting> paintings;
in >> paintings;

如果 Painting不支持<<或>>操作符,则将产生编译器错误。为自定义数据类型提供流操作符的另一个好处是:可以将这些数据类型的值存储为 QVariant 的形式,这便于它们在更大的范围内使用,例如通过 QSettings。这些工作都是假设预先使用 qRegisterMetaTypeStreamOperators<T>()注册了数据类型。

当用至QDataStream,Qt 考虑了读取和写入的每一个数据类型,包括具有任意多的项的容器类。这使我们不必再组织排列所写入的数据,也不必再对所读入的数据进行解析。我们唯一的任务是确保读取各种数据类型时的顺序与写入时的严格一致,而细节上的问题就留给 Qt 去处理了。

QDataStream 对用户自定义应用程序文件格式和标准二进制数据格式都是有用处的。我们可以使用流式操作符或者 readRawBytes()和writeRawBytes()在基本数据类型(如quint16或float) 上读取和写入标准二进制数据格式。如果QDataStream 仅仅只用于读写基本 C++ 数据类型,甚至都不必调用setVersion()函数。

到目前为止,我们使用硬编码的流版本号QDataStream::Qt_4_3 来载入和保存数据。这种方式是简单可靠的,但是它也有二个小缺点:不能利用新的版本或者更新的格式。例如,如果Qt的一个更新版本为 QFont 添加了一个新的属性(即除了它的点大小、族属性等之外的属性),而硬编码的版本号为Qt_4_3,那么将不能保存或者载入这个属性。针对这个情况,有两种解决方案。第一种解决方案是把 QDataStream 版本号嵌入到文件中:

QDataStream out(&file);
out << quint32(MagicNumber) << quint16(out.version());

(其中, MagicNumber 表示唯一一个标识文件类型的常数)。这种方法可以确保在任何情况下都使用最新的 QDataStream 版本来写入数据。当读取文件时,将读取流版本:

quint32 magic;
quint16 streamVersion;QDataStream in(&file);
in >> magic >>streamVersion;if(magic != MagicNumber)
{std::cerr << "File is not recognized by this application"<< std::endl;
else if(streamVersion > in.version())
{std::cerr << "File is from a more recent version of the " << "application" << std::endl;return false;
}
in.serVersion(streamVersion);

只要流版本小于或等于应用程序所使用的版本,就可以读取数据;否则报告一个错误。

如果文件格式包含一个它自己的版本号,则可以利用它来推断出流版本号而不是明确地存储它。例如,假设文件格式是针对应用程序的1.3版本,然后可以如下编写代码:

QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_3);
out << quint32(MagicNumber) << quint16(0x0103);

当回读数据时,可根据应用程序的版本号来决定所使用的 QDataStream版本:

QDataStream in(&file);
in >> magic >> appVersion;if(magic != MagicNumber)
{   std::cerr << "File is not recognized by this application"<< std::endl;return false;
}
else if(appVersion > 0x0103)
{std::cerr << "File is from a more recent version of the "<< "applicaition"<< std::endl;return false;
}
if(appVersion < 0x0103)
{in.setVersion(QDataStream::Qt_3_0));
}
else
{in.setVersion(QDataStream::Qt_4_3));
}

在这个实例中,我们规定由这个应用程序1.3版本之前的版本所保存的任何文件都使用数据流版本4(Qt_3_0),而由这个应用程序的1.3版本保存的文件使用数据流版本9(Qt_4_3)。

总之,一共有三种处理QDataStream版本的策略:硬编码的版本号、明确地写入并读取版本号以及根据应用程序版本号使用不同的硬编码版本号。这些策略都可以用来确保由应用程序的较老版本写入的数据可以被新版本成功读取,即使这个新版本的应用程序与更新的Qt版本关联。一旦选择了一种处理QDataStream版本的策略,使用Qt读取和写入二进制数据时就变得简单且可靠。

如果想一次读取或写入一个文件,可以完全不用QDataStream而使用QIODevice的write()和readAll()函数。例如:

bool copyFile(const QString &source, const QString &dest)
{QFile sourceFile(source);if(!sourceFile.open(QIODevice::ReadOnly))return false;QFile destFile(dest);if(!destFile.open(QIODevice::WriteOnly))return false;destFile.write(sourceFile.readAll());return sourceFile.error() == QFile::NoError && destFile.error() == QFile::NoError;
}

在调用readAll()的那一行中,输入文件的所有内容都被读入到一个QByteArray中,然后将它传给write()函数以写到输出文件中。虽然获得QByteArray中的所有数据比逐项读取数据需要更多的内存,但是它也带来了一些方便。例如,在这之后可以使用qCompress()和qUncompress()函数来压缩和解压缩数据。与qCompress()和qUncompress()功能相当的另外一个不占内存的方法是使用Qt解决方案中的QtIOCompressor。QtIOCompressor压缩它写入的数据流,解压它要读入的数据流,并不将整个文件存储在内存中。

其实还有比使用QDataStream更加合适的直接读取QIODevice的方案。除了一个字节都不能读(unread)的ungetChar()函数,QIODevice还提供了peek()函数,它不能在不移动设备位置时返回下一个数据字节。这不仅对随机存取设备(如文件)有效,同时也对顺序存储设备(如网络套接)有效。另外,还有一个seek()函数用来设置设备的位置,它主要用以支持随机存取的设备。

Qt4_读取和写入二进制数据相关推荐

  1. 注册表写入二进制数据

    注册表写入二进制数据 原文:http://bbs.csdn.net/topics/390237976 uses Registry; procedure TForm1.Button1Click(Send ...

  2. 【Pandas入门教程】如何读取和写入表格数据

    如何读取和写入表格数据 来源:Pandas官网:https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html 文章 ...

  3. python csv使用_Python基于csv模块实现读取与写入csv数据的方法

    本文实例讲述了Python基于csv模块实现读取与写入csv数据的方法.分享给大家供大家参考,具体如下: 通过csv模块可以轻松读取格式为csv的文件,而且csv模块是python内置的,不需要下载就 ...

  4. python二进制文件 删除尾部数据_在Python中读取和切片二进制数据文件的最快方法...

    我有一个处理脚本,用于提取"uint16"类型的二进制数据文件,并一次以6400块的形式进行各种处理.该代码最初是用Matlab编写的,但由于分析代码是用 Python编写的,我们 ...

  5. Qt4_读取和写入文本

    虽然二进制文件格式比通常基于文本的格式更加紧凑,但是它们是机器语言,无法人工阅读或者编辑.在二进制文件格式无法适用的场合,可以使用文本格式来代替. Qt 提供了 QTextStream类读写纯文本文件 ...

  6. QFile和C语言对文件操作的性能比较.--读取double型二进制数据文件

    关键问题在于:QFile读取double型二进制数据流,只有两种方法处理数值. 一是通过QDataStream一个一个double读取,存储在一个QVector<double>中 二是通过 ...

  7. h5如何上传文件二进制流_前端H5中JS用FileReader对象读取blob对象二进制数据,文件传输...

    HTML5中的Blob对象只是二进制数据的容器,本身并不能操作二进制,故本篇将对其操作对象FileReader进行介绍. FileReader FileReader主要用于将文件内容读入内存,通过一系 ...

  8. 读取、写入excel数据

    在实际项目中,不可避免的会操作excel表格.一直以来都是读取excel表格,可今天为了写入excel表格,可是煞费苦心,终于完成,记录下来以便后续使用. 1.读取excel表格的数据 读取excel ...

  9. 用c语言读取和写入文件数据

    任务需求 用notepad++编辑一个test.c文件 要求从int.txt中读取数据 经过开平方和平方计算 输出到另一个out.txt文件中 程序编写 #include <stdio.h> ...

最新文章

  1. c# 第六课 linq
  2. linux怎么修改sftp默认端口,如何在 Linux 系统中如何更改 SFTP 端口
  3. 如何查看linux动态链接库文件的版本等其他信息
  4. 解决Tensorflow 使用时cpu编译不支持警告
  5. 单片机上使用TEA加密通信(转)
  6. 关于android.view.WindowLeaked异常的解决方案
  7. java线程变量值_JAVA线程中对变量值的修改
  8. python-往对象中添加属性值
  9. 浅析crontab命令
  10. 16 数值的整数次方 (第3章 高质量的代码-代码的完整性)
  11. python压缩视频_如何压缩视频大小?
  12. GitHub上的免费中文编程书
  13. 第一次CSP认证110分
  14. HDU 2448 Mining Station on the Sea(Floyd+最优匹配)
  15. 国内外主流云主机 | 对比分析 - 阿里云与Digital Ocean
  16. [Unity-3D]粒子系统--粒子流黑洞
  17. 学习UI设计有哪些入门的小技巧
  18. oracle获取指定日期内工作日的天数或节假日天数
  19. 【PTA】7-6 海盗分赃 (Java)
  20. 在macbook air中通过usb安装windows7操作系统

热门文章

  1. 根据varchar排列是怎么比大小的_骨架大小怎么看?肩宽、胸腔宽、胯宽是关键,加码大骨架穿搭技巧...
  2. mysql 获取下一条记录数,如何在MySQL中查询当前数据上一条和下一条的记录
  3. 浅析libuv源码-node事件轮询解析(1)
  4. 培生同意以3亿美元出售华尔街英语
  5. android-activity生命周期方法
  6. Ceph的客户端安装
  7. 数据存储-CoreData总结
  8. 无法获得锁 /var/cache/apt/archives/lock – open (11 资源临时不可用)
  9. awk 正则表达式、正则运算符详细介绍
  10. 解决Vue项目报错:Expected indentation of 2 spaces but found 4. eslint(indent) [8, 1]的方法