• Qt_IO系统_二进制读写


    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可以很方便地对文件、网络套接字等进行读写操作。我们从代码开始看起:

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

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

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

    Qt 是如何存储数据的

    例如char *字符串。

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

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

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

    重新运行一下程序。

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

    QFile file("file.dat");
    file.open(QIODevice::ReadOnly);
    QDataStream in(&file);
    QString str;
    qint32 a;
    in >> str >> a;
    

    需要注意的是:

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

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

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

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

    魔术数字

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

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

    文件版本

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

    Qt 版本

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

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

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

    QFile file("file.dat");
    file.open(QIODevice::ReadOnly);
    QDataStream in(&file);
    
    // 检查魔术数字
    quint32 magic;
    in >> magic;
    if (magic != 0xA0B0C0D0) {
        return BAD_FILE_FORMAT;
    }
    
    // 检查版本
    qint32 version;
    in >> version;
    if (version < 100) {
        return BAD_FILE_TOO_OLD;
    }
    if (version > 123) {
        return BAD_FILE_TOO_NEW;
    }
    
    if (version <= 110) {
        in.setVersion(QDataStream::Qt_3_2);
    } else {
        in.setVersion(QDataStream::Qt_4_0);
    }
    // 读取数据
    in >> lots_of_interesting_data;
    if (version >= 120) {
        in >> data_new_in_version_1_2;
    }
    in >> other_interesting_data;
    

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

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

    什么是流的形式

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

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

    QFile file("file.dat");
    file.open(QIODevice::ReadWrite);
    
    QDataStream stream(&file);
    QString str = "the answer is 42";
    QString strout;
    
    stream << str;
    file.flush();
    stream >> strout;
    

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

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

    完整的Demo

    #include <qapplication.h>
    #include <qfile.h>
    #include <qdebug.h>
    #include <qdatastream.h>
    #include <qdir.h>
    
    namespace tudou
    {
    // *******************
    //  writeData
    // *******************
    void writeData(){
    
        qDebug() << QDir::currentPath();
        //对于mac平台需要注意,当前执行目录不是bulid下。
        //而是xxx.app/Contents/MacOS
    
        QFile file("file.dat");  // 没有文件会创建文件
        if(!file.open(QIODevice::WriteOnly)){
            qWarning() <<"open fail";
        }else {
            qDebug() << "write data...";
            QDataStream out(&file);
            out << QString("this answer is.......");
            out << (qint32)42;
            qDebug() << "write sucess";
        }
        file.close();
    }
    // *******************
    //  readData
    // *******************
    void readData(){
        QFile fileRead("file.dat");
        if(!fileRead.open(QIODevice::ReadOnly)){
            qWarning() <<"open fail";
        }else {
            qDebug() << "read Data Start....";
            QDataStream in(&fileRead);
            QString str;
            qint32 number;
            in >> str >> number;
            qDebug() << str << " " << number;
            qDebug() << "read Data complete";
        }
    
        fileRead.close();
    }
    
    // *******************
    //  writeDataByMagic
    // *******************
    void writeDataByMagic(){
    
        QFile file("file.dat");
        if(!file.open(QIODevice::WriteOnly)){
            qWarning() <<"open fail";
        }else {
            qDebug() << "write data...";
            QDataStream dataStream(&file);
    
            dataStream << static_cast<quint32>(0xA0B0C0D0); // 写入魔术数字
            dataStream << static_cast<qint32>(123);// 标识文件的版本
            dataStream.setVersion(QDataStream::Qt_5_0); // 设置安装那个Qt版本号去读写
            // 写入数据
            dataStream << QString("test text content");
            dataStream << static_cast<qint32>(28);
            qDebug() << "write sucess";
        }
    }
    // *******************
    //  readDataByMagic
    // *******************
    void readDataByMagic(){
        QFile file(QString("file.dat"));
        if(!file.open(QIODevice::ReadOnly)){
            qWarning()  << "open fail";
        }else {
            QDataStream dataStream(&file);
            // 检查魔术数字
            quint32 maigc;
            dataStream >> maigc;
            if(maigc!=0xA0B0C0D0){
                qWarning() << "file magic error";
                return;
            }
            // 检查版本 假如支持的文件版本为 110~123
            qint32 fileVersion;
            dataStream >> fileVersion;
            if(fileVersion > 123 || fileVersion < 100){
                qDebug() << "file Version error";
                return;
            }
    
            if(fileVersion <= 110) {
                dataStream.setVersion(QDataStream::Qt_4_0);
            }else {
                dataStream.setVersion(QDataStream::Qt_5_0);
            }
    
            // 读取数据
            QString stringData;
            qint32 intData;
            dataStream >> stringData >> intData;
            qDebug() <<"读取数据为: " <<  stringData << intData;
        }
    }
    
    } //namespace tudou
    
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        //    tudou::writeData();
        //    tudou::readData();
        tudou::writeDataByMagic();
        tudou::readDataByMagic();
    
        return a.exec();
    }
    
  • 相关阅读:
    解决Tomcat请求中文乱码的问题
    Bootstrap 兼容 IE
    Spring+MyBatis多数据源配置实现
    年度总结与计划
    AJAX 提交表单以及文件上传
    移动端Web开发调试工具:Chrome DevTools
    SpringMVC 之 @ResponseBody 和 @RequestBody
    goland快键键防忘
    MySQL日志文件影响数据库的各种类型活动
    何为云计算
  • 原文地址:https://www.cnblogs.com/__tudou__/p/12124318.html
Copyright © 2020-2023  润新知