Qt中的读写

Qt中,除了QIODevice提供了read()readLine()readAll()以及write()等基本的操作。同时,Qt 还提供了更高一级的操作:用于二进制的流QDataStream和用于文本流的QTextStream。本节,我们将讲解有关QDataStream的使用以及一些技巧。下一篇介绍QTextStream的相关内容

QDataStream

QDataStream提供了基于QIODevice的二进制数据的序列化。数据流是一种二进制流,这种流完全不依赖于底层操作系统、CPU 或者字节顺序(大端或小端)。例如,在安装了 Windows 平台的 PC 上面写入的一个数据流,可以不经过任何处理,直接拿到运行了 Solaris 的 SPARC 机器上读取。由于数据流就是二进制流,因此我们也可以直接读写没有编码的二进制数据,例如图像、视频、音频等

QDataStream

QDataStream既能够存取 C++ 基本类型,如 int、char、short 等,也可以存取复杂的数据类型,例如自定义的类。实际上,QDataStream对于类的存储,是将复杂的类分割为很多基本单元实现的。

结合QIODeviceQDataStream可以很方便地对文件、网络套接字等进行读写操作。我们从代码开始看起:

  1. QFile file("file.dat");
  2. file.open(QIODevice::WriteOnly);
  3. QDataStream out(&file);
  4. out << QString("the answer is");
  5. out << (qint32)42;

在这段代码中,我们首先打开一个名为 file.dat 的文件(注意,我们为简单起见,并没有检查文件打开是否成功,这在正式程序中是不允许的)。然后,我们将刚刚创建的file对象的指针传递给一个QDataStream实例out。类似于std::cout标准输出流,QDataStream也重载了输出重定向<<运算符。后面的代码就很简单了:将“the answer is”和数字 42 输出到数据流。由于我们的 out 对象建立在file之上,因此是写入数据到file

需要指出一点:最好使用 Qt 整型来进行读写,比如程序中的qint32。这保证了在任意平台和任意编译器都能够有相同的行为。

Qt 是如何存储数据的

例如char *字符串。

  • 在存储时,会首先存储该字符串包括 \0 结束符的长度(32位整型)然后是字符串的内容以及结束符 \0。
  • 在读取时,先以 32 位整型读出整个的长度,然后按照这个长度取出整个字符串的内容。

但是,如果你直接运行这段代码,你会得到一个空白的 file.dat,并没有写入任何数据。这是因为我们的file没有正常关闭。为性能起见,数据只有在文件关闭时才会真正写入。因此,我们必须在最后添加一行代码:

  1. file.close(); // 如果不想关闭文件,可以使用 file.flush();

重新运行一下程序。

下面,我们要将这个答案读取出来:

  1. QFile file("file.dat");
  2. file.open(QIODevice::ReadOnly);
  3. QDataStream in(&file);
  4. QString str;
  5. qint32 a;
  6. in >> str >> a;

需要注意的是:

你必须按照写入的顺序,将数据读取出来。也就是说,程序数据写入的顺序必须预先定义好。在这个例子中,我们首先写入字符串,然后写入数字,那么就首先读出来的就是字符串,然后才是数字。顺序颠倒的话,程序行为是不确定的,严重时会直接造成程序崩溃。

如何保证读取数据的正确性? --> 魔术数字,文件版本,Qt版本

由于二进制流是纯粹的字节数据,带来的问题是,如果程序不同版本之间按照不同的方式读取(前面说过,Qt 保证读写内容的一致,但是并不能保证不同 Qt 版本之间的一致),数据就会出现错误。因此,我们必须提供一种机制来确保不同版本之间的一致性。通常,我们会使用如下的代码写入:

  1. QFile file("file.dat");
  2. file.open(QIODevice::WriteOnly);
  3. QDataStream out(&file);
  4. // 写入魔术数字和版本
  5. out << (quint32)0xA0B0C0D0; //设置魔术数字
  6. out << (qint32)123; //标识文件的版本
  7. out.setVersion(QDataStream::Qt_4_0); //指定 Qt 按照哪个Qt版本去读.因为Qt有很多版本
  8. // 写入数据
  9. out << lots_of_interesting_data;

魔术数字

所谓魔术数字,是二进制输出中经常使用的一种技术。二进制格式是人不可读的,并且通常具有相同的后缀名(比如 dat 之类),因此我们没有办法区分两个二进制文件哪个是合法的。所以,我们定义的二进制格式通常具有一个魔术数字,用于标识文件的合法性。

在本例中,我们在文件最开始写入 0xA0B0C0D0,在读取的时候首先检查这个数字是不是 0xA0B0C0D0。如果不是的话,说明这个文件不是可识别格式,因此根本不需要去继续读取。一般二进制文件都会有这么一个魔术数字,例如 Java 的 class 文件的魔术数字就是 0xCAFEBABE,使用二进制查看器就可以查看。魔术数字是一个 32 位的无符号整型,因此我们使用quint32来得到一个平台无关的 32 位无符号整型

文件版本

  1. out << (qint32)123; //标识文件的版本

Qt 版本

Qt 不同版本之间的读取方式可能也不一样。这样,我们就得指定 Qt 按照哪个版本去读。这里,我们指定以 Qt 4.0 格式去读取内容。

读取添加了魔数和版本号的文件

当我们这样写入文件之后,我们在读取的时候就需要增加一系列的判断

  1. QFile file("file.dat");
  2. file.open(QIODevice::ReadOnly);
  3. QDataStream in(&file);
  4. // 检查魔术数字
  5. quint32 magic;
  6. in >> magic;
  7. if (magic != 0xA0B0C0D0) {
  8. return BAD_FILE_FORMAT;
  9. }
  10. // 检查版本
  11. qint32 version;
  12. in >> version;
  13. if (version < 100) {
  14. return BAD_FILE_TOO_OLD;
  15. }
  16. if (version > 123) {
  17. return BAD_FILE_TOO_NEW;
  18. }
  19. if (version <= 110) {
  20. in.setVersion(QDataStream::Qt_3_2);
  21. } else {
  22. in.setVersion(QDataStream::Qt_4_0);
  23. }
  24. // 读取数据
  25. in >> lots_of_interesting_data;
  26. if (version >= 120) {
  27. in >> data_new_in_version_1_2;
  28. }
  29. in >> other_interesting_data;

这段代码就是按照前面的解释进行的。

首先读取魔术数字,检查文件是否合法。如果合法,读取文件版本:小于 100 或者大于 123 都是不支持的。如果在支持的版本范围内(100 <= version <= 123),则当是小于等于 110 的时候,按照Qt_3_2的格式读取,否则按照Qt_4_0的格式读取。当设置完这些参数之后,开始读取数据。

什么是流的形式

至此,我们介绍了有关QDataStream的相关内容。

那么,既然QIODevice提供了read()readLine()之类的函数,为什么还要有QDataStream呢?QDataStreamQIODevice有什么区别?区别在于,QDataStream提供流的形式,性能上一般比直接调用原始 API 更好一些。我们通过下面一段代码看看什么是流的形式:

  1. QFile file("file.dat");
  2. file.open(QIODevice::ReadWrite);
  3. QDataStream stream(&file);
  4. QString str = "the answer is 42";
  5. QString strout;
  6. stream << str;
  7. file.flush();
  8. stream >> strout;

在这段代码中,我们首先向文件中写入数据,紧接着把数据读出来。有什么问题吗?运行之后你会发现,strout实际是空的。为什么没有读取出来?我们不是已经添加了file.flush();语句吗?原因并不在于文件有没有写入,而是在于我们使用的是“流”。所谓流,就像水流一样,它的游标会随着输出向后移动。当使用<<操作符输出之后,流的游标已经到了最后,此时你再去读,当然什么也读不到了。所以你需要在输出之后重新把游标设置为 0 的位置才能够继续读取。具体代码片段如下:

  1. stream << str;
  2. stream.device()->seek(0);
  3. stream >> strout;

完整的Demo

  1. #include <qapplication.h>
  2. #include <qfile.h>
  3. #include <qdebug.h>
  4. #include <qdatastream.h>
  5. #include <qdir.h>
  6. namespace tudou
  7. {
  8. // *******************
  9. // writeData
  10. // *******************
  11. void writeData(){
  12. qDebug() << QDir::currentPath();
  13. //对于mac平台需要注意,当前执行目录不是bulid下。
  14. //而是xxx.app/Contents/MacOS
  15. QFile file("file.dat"); // 没有文件会创建文件
  16. if(!file.open(QIODevice::WriteOnly)){
  17. qWarning() <<"open fail";
  18. }else {
  19. qDebug() << "write data...";
  20. QDataStream out(&file);
  21. out << QString("this answer is.......");
  22. out << (qint32)42;
  23. qDebug() << "write sucess";
  24. }
  25. file.close();
  26. }
  27. // *******************
  28. // readData
  29. // *******************
  30. void readData(){
  31. QFile fileRead("file.dat");
  32. if(!fileRead.open(QIODevice::ReadOnly)){
  33. qWarning() <<"open fail";
  34. }else {
  35. qDebug() << "read Data Start....";
  36. QDataStream in(&fileRead);
  37. QString str;
  38. qint32 number;
  39. in >> str >> number;
  40. qDebug() << str << " " << number;
  41. qDebug() << "read Data complete";
  42. }
  43. fileRead.close();
  44. }
  45. // *******************
  46. // writeDataByMagic
  47. // *******************
  48. void writeDataByMagic(){
  49. QFile file("file.dat");
  50. if(!file.open(QIODevice::WriteOnly)){
  51. qWarning() <<"open fail";
  52. }else {
  53. qDebug() << "write data...";
  54. QDataStream dataStream(&file);
  55. dataStream << static_cast<quint32>(0xA0B0C0D0); // 写入魔术数字
  56. dataStream << static_cast<qint32>(123);// 标识文件的版本
  57. dataStream.setVersion(QDataStream::Qt_5_0); // 设置安装那个Qt版本号去读写
  58. // 写入数据
  59. dataStream << QString("test text content");
  60. dataStream << static_cast<qint32>(28);
  61. qDebug() << "write sucess";
  62. }
  63. }
  64. // *******************
  65. // readDataByMagic
  66. // *******************
  67. void readDataByMagic(){
  68. QFile file(QString("file.dat"));
  69. if(!file.open(QIODevice::ReadOnly)){
  70. qWarning() << "open fail";
  71. }else {
  72. QDataStream dataStream(&file);
  73. // 检查魔术数字
  74. quint32 maigc;
  75. dataStream >> maigc;
  76. if(maigc!=0xA0B0C0D0){
  77. qWarning() << "file magic error";
  78. return;
  79. }
  80. // 检查版本 假如支持的文件版本为 110~123
  81. qint32 fileVersion;
  82. dataStream >> fileVersion;
  83. if(fileVersion > 123 || fileVersion < 100){
  84. qDebug() << "file Version error";
  85. return;
  86. }
  87. if(fileVersion <= 110) {
  88. dataStream.setVersion(QDataStream::Qt_4_0);
  89. }else {
  90. dataStream.setVersion(QDataStream::Qt_5_0);
  91. }
  92. // 读取数据
  93. QString stringData;
  94. qint32 intData;
  95. dataStream >> stringData >> intData;
  96. qDebug() <<"读取数据为: " << stringData << intData;
  97. }
  98. }
  99. } //namespace tudou
  100. int main(int argc, char *argv[])
  101. {
  102. QApplication a(argc, argv);
  103. // tudou::writeData();
  104. // tudou::readData();
  105. tudou::writeDataByMagic();
  106. tudou::readDataByMagic();
  107. return a.exec();
  108. }

Qt_IO系统_二进制读写的更多相关文章

  1. Qt_IO系统_文件

    主要参考: devbean.net 豆子的博客 参考书:<QtCreator 快速入门>第三版 目录 QFile 如何使用QFile QFile 和QFileInfo Demo 文件操作是 ...

  2. 文本读写vs二进制读写

    [文本读写vs二进制读写] 在学习C语言文件操作后,我们都会知道打开文件的函数是fopen,也知道它的第二个参数是 标志字符串.其中,如果字符串中出现'b',则表明是以打开二进制(binary)文件, ...

  3. C++文件操作(输入输出、格式控制、文件打开模式、测试流状态、二进制读写)

    1.向文件写数据 头文件#include <ofstream> ①Create an instance of ofstream(创建ofstream实例) ②Open the file w ...

  4. BZOJ_3932_[CQOI2015]任务查询系统_主席树

    BZOJ_3932_[CQOI2015]任务查询系统_主席树 题意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,P ...

  5. C/C++二进制读写png文件

    以下代码只有最简单的读写.地址定位啥的,个别注释中有.如果要改动png的格式甚么的就要再了解一下png的数据结构如果要十进制的话就跟着注释改一下: /*! * \file CC++二进制读写png文件 ...

  6. 10.11 android输入系统_补充知识_activity_window_decor_view关系

    android里:1个application, 有1个或多个activity(比如支付宝有:首页.财富.口碑.朋友.我的,这些就是activity)1个activity, 有1个window(每个ac ...

  7. 9.2 Binder系统_驱动情景分析_服务注册过程

    1. 几个重要结构体的引入给test_server添加一个goodbye服务, 由此引入以下概念: 进程间通信其实质也是需要三要素:源.目的.数据,源是自己,目的用handle表示:通讯的过程是源向实 ...

  8. MATLAB 通过二进制读写文件

    这几天在做信息隐藏方面的应用,在读写文本文件时耗费许久,故特别的上网学习一二,这里给出一常用读写,其他的都类似. 很多时候,我们都要将一个.txt以二进制方式读出来,操作后在恢复成.txt文本. ma ...

  9. APUE学习笔记——5.9Binary I/O 二进制读写

    概述:         二进制I/O,通常用来一次性读写结构化的数据块.(因为有时候我们要读写的数据中包含换行符或者Null字符,无法使用fgets或fputs,而使用getc和putc又需要花费太多 ...

随机推荐

  1. PageHelper支持GreenPlum

    greenplum是pivotal在postgresql的基础上修改的一个数据库,语法和postgresql通用.使用PageHelper做分页插件的时候,发现目前没有针对greenplum做支持,但 ...

  2. yii2.0数据库操作

    User::find()->all(); 此方法返回所有数据: User::findOne($id); 此方法返回 主键 id=1 的一条数据(举个例子): User::find()->w ...

  3. ca72a_c++_标准IO库:面向对象的标准库

    /*ca72a_c++_标准IO库:面向对象的标准库继承:基类->派生类3个头文件9个标准库类型IO对象不可复制或赋值 ofstream, f--file,文件输出流ostringstream, ...

  4. WeChair项目Beta冲刺(5/10)

    团队项目进行情况 1.昨日进展    Beta冲刺第五天 昨日进展: 前后端并行开发,项目按照计划有条不絮进行 2.今日安排 前端:扫码占座功能和预约功能并行开发 后端:扫码占座后端逻辑开发,预约用座 ...

  5. C语言实现类

    #ifndef __DEFINE__H__ #define __DEFINE__H__ #define vector3(type) \ typedef struct vector3_##type { ...

  6. 深入理解跨域SSO单点登录原理与技术

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 一:SSO体系结 ...

  7. 3、尚硅谷_SSM高级整合_使用ajax操作实现增加员工的功能

    20.尚硅谷_SSM高级整合_新增_创建员工新增的模态框.avi 1.接下来当我们点击增加按钮的时候会弹出一个员工信息的对话框 知识点1:当点击新增的时候会弹出一个bootstrap的一个模态对话框 ...

  8. 01.scrapy入门

    Scrapy快速入门 Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,它使用Twisted这个异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求. ...

  9. Python-argparse模块-获取命令行参数

    #!/usr/bin/python3 """ Author : Jet Bi License : www.cyeap.com Summary : 获取命令行的参数 Not ...

  10. git和github入门指南(4)

    4.分支管理 4.1.什么是分支? 分支可以简单的理解为分叉,在主干上长出一条枝干出来,主干分支通常叫做master,在主分支上可以开很多分支出来,不同的人在不同的分支上做开发,最后合并到主分支,这样 ...