QDataStream入门

QDataStream 是 Qt 提供的一个用于二进制数据序列化的类,它可以将各种数据类型转换为字节流,也可以将字节流转换回原始数据。适合用于二进制数据的持久化和网络传输。与 QFileQTcpSocket 等 I/O 设备配合使用。

QDataStream 属性、方法

属性

属性 类型 描述
byteOrder QDataStream::ByteOrder 字节序(大端或小端)
floatingPointPrecision QDataStream::FloatingPointPrecision 浮点数精度(单精度或双精度)
status QDataStream::Status 数据流状态
version int 数据流版本(用于兼容性控制)

主要方法

构造函数

方法 描述
QDataStream() 创建空的数据流
QDataStream(QIODevice *) 关联到指定的 I/O 设备
QDataStream(QByteArray *, QIODevice::OpenMode) 关联到字节数组

基本操作

方法 描述
QIODevice *device() const 获取关联的设备
void setDevice(QIODevice *) 设置关联的设备
void unsetDevice() 解除设备关联

流控制

方法 描述
void skipRawData(int len) 跳过指定字节数的数据
int readRawData(char *, int) 读取原始数据
int writeRawData(const char *, int) 写入原始数据
bool atEnd() const 是否到达流末尾

状态管理

方法 描述
QDataStream::Status status() const 获取当前状态
void resetStatus() 重置状态为 Ok
void setStatus(QDataStream::Status) 设置流状态

配置方法

方法 描述
void setByteOrder(QDataStream::ByteOrder) 设置字节序
QDataStream::ByteOrder byteOrder() const 获取当前字节序
void setFloatingPointPrecision(QDataStream::FloatingPointPrecision) 设置浮点数精度
QDataStream::FloatingPointPrecision floatingPointPrecision() const 获取当前浮点数精度
void setVersion(int) 设置流版本
int version() const 获取当前流版本

枚举类型

ByteOrder

描述
BigEndian 大端字节序(网络字节序)
LittleEndian 小端字节序(Intel x86字节序)

FloatingPointPrecision

描述
SinglePrecision 32位单精度浮点数
DoublePrecision 64位双精度浮点数

Status

描述
Ok 操作成功
ReadPastEnd 尝试读取超过流末尾
ReadCorruptData 读取到损坏数据
WriteFailed 写入操作失败

重载运算符

运算符 支持的数据类型
operator<< 基本类型、QString、QByteArray、Qt容器等
operator>> 基本类型、QString、QByteArray、Qt容器等

支持的常用内置类型

  • 基本类型:bool, char, short, int, long long, float, double

  • Qt 类型:QString, QByteArray, QBitArray, QDate, QTime, QDateTime, QUrl, QUuid

  • Qt 容器:QList, QVector, QMap, QHash, QSet

基本用法

写入数据到流

cpp

复制代码
#include <QDataStream>
#include <QFile>

// 写入数据到文件
QFile file("data.bin");
if (file.open(QIODevice::WriteOnly)) {
    QDataStream out(&file);
    
    out << QString("Hello, Qt!");  // 写入字符串
    out << 3.1415926;              // 写入双精度浮点数
    out << 42;                     // 写入整数
    
    file.close();
}

从流中读取数据

cpp

复制代码
#include <QDataStream>
#include <QFile>

// 从文件读取数据
QFile file("data.bin");
if (file.open(QIODevice::ReadOnly)) {
    QDataStream in(&file);
    
    QString str;
    double d;
    int i;
    
    in >> str >> d >> i;  // 注意读取顺序要与写入顺序一致
    
    qDebug() << str << d << i;
    
    file.close();
}

版本控制

QDataStream 使用版本号来确保数据兼容性:

cpp

复制代码
QDataStream out(&file);
out.setVersion(QDataStream::Qt_5_15);  // 设置版本

QDataStream in(&file);
in.setVersion(QDataStream::Qt_5_15);   // 读取时使用相同版本

自定义数据类型

你可以为自定义类型重载 <<>> 运算符:

cpp

复制代码
struct Person {
    QString name;
    int age;
};

QDataStream &operator<<(QDataStream &out, const Person &p) {
    out << p.name << p.age;
    return out;
}

QDataStream &operator>>(QDataStream &in, Person &p) {
    in >> p.name >> p.age;
    return in;
}

// 使用
Person p {"Alice", 30};
QDataStream out(&file);
out << p;

与 QByteArray 结合使用

cpp

复制代码
QByteArray byteArray;
QDataStream stream(&byteArray, QIODevice::WriteOnly);
stream << "Qt data stream example";

// 从字节数组读取
QDataStream in(byteArray);
QString str;
in >> str;

QDataStream 结构体序列化与反序列化

在 Qt 中使用 QDataStream 对结构体进行序列化和反序列化,主要通过重载 <<>> 运算符来实现。以下是详细的使用方法:

基本结构体序列化

1. 定义结构体并重载运算符

cpp

复制代码
#include <QDataStream>

// 自定义结构体
struct Person {
    QString name;
    int age;
    double height;
    
    // 可选:添加构造函数方便使用
    Person() : age(0), height(0.0) {}
    Person(const QString &n, int a, double h) : name(n), age(a), height(h) {}
};

// 重载输出运算符 <<
QDataStream &operator<<(QDataStream &out, const Person &person) {
    out << person.name << person.age << person.height;
    return out;
}

// 重载输入运算符 >>
QDataStream &operator>>(QDataStream &in, Person &person) {
    in >> person.name >> person.age >> person.height;
    return in;
}

2. 使用示例

cpp

复制代码
// 写入结构体到文件
void writePersonToFile(const QString &filename, const Person &person) {
    QFile file(filename);
    if (file.open(QIODevice::WriteOnly)) {
        QDataStream out(&file);
        out.setVersion(QDataStream::Qt_5_15); // 设置版本
        out << person; // 使用重载的运算符
        file.close();
    }
}

// 从文件读取结构体
Person readPersonFromFile(const QString &filename) {
    Person person;
    QFile file(filename);
    if (file.open(QIODevice::ReadOnly)) {
        QDataStream in(&file);
        in.setVersion(QDataStream::Qt_5_15); // 与写入时版本一致
        in >> person; // 使用重载的运算符
        file.close();
    }
    return person;
}

// 使用示例
Person p1("张三", 30, 175.5);
writePersonToFile("person.dat", p1);

Person p2 = readPersonFromFile("person.dat");
qDebug() << "Name:" << p2.name << "Age:" << p2.age << "Height:" << p2.height;

包含容器成员的结构体

如果结构体包含容器成员(如 QList、QVector 等),同样可以序列化:

cpp

复制代码
struct Team {
    QString teamName;
    QList<Person> members;
};

QDataStream &operator<<(QDataStream &out, const Team &team) {
    out << team.teamName << team.members;
    return out;
}

QDataStream &operator>>(QDataStream &in, Team &team) {
    in >> team.teamName >> team.members;
    return in;
}

版本兼容性处理

对于可能需要变更的结构体,可以添加版本控制:

cpp

复制代码
struct Employee {
    QString name;
    int id;
    QString department;
    // 新版本添加的字段
    QDate hireDate;
};

QDataStream &operator<<(QDataStream &out, const Employee &emp) {
    out << emp.name << emp.id << emp.department;
    // 只在较新版本中写入hireDate
    if (out.version() >= QDataStream::Qt_5_12) {
        out << emp.hireDate;
    }
    return out;
}

QDataStream &operator>>(QDataStream &in, Employee &emp) {
    in >> emp.name >> emp.id >> emp.department;
    // 如果流版本足够新且有数据,读取hireDate
    if (in.version() >= QDataStream::Qt_5_12 && !in.atEnd()) {
        in >> emp.hireDate;
    } else {
        emp.hireDate = QDate(); // 设为无效日期
    }
    return in;
}

结构体转换为字节流

方法1:直接内存拷贝(推荐用于POD)

cpp

复制代码
#pragma pack(push, 1)  // 1字节对齐
struct PodExample {
    int id;
    double value;
    char name[32];
};
#pragma pack(pop)      // 恢复默认对齐

// 转换为字节流
QByteArray structToByteArray(const PodExample &data) {
    // 直接内存拷贝
    return QByteArray(reinterpret_cast<const char*>(&data), sizeof(PodExample));
}

// 从字节流恢复
PodExample byteArrayToStruct(const QByteArray &byteArray) {
    PodExample data;
    if(byteArray.size() == sizeof(PodExample)) {
        memcpy(&data, byteArray.constData(), sizeof(PodExample));
    }
    return data;
}

方法2:使用QDataStream(更通用但稍慢)

cpp

复制代码
QByteArray structToByteArraySafe(const PodExample &data) {
    QByteArray byteArray;
    QDataStream stream(&byteArray, QIODevice::WriteOnly);
    //stream.setByteOrder(QDataStream::LittleEndian);默认就是大端对齐
    stream.writeRawData(reinterpret_cast<const char*>(&data), sizeof(PodExample));
    return byteArray;
}

PodExample byteArrayToStructSafe(const QByteArray &byteArray) {
    PodExample data;
    if(byteArray.size() == sizeof(PodExample)) {
        QDataStream stream(byteArray);
         //stream.setByteOrder(QDataStream::BigEndian);默认就是大端对齐
        stream.readRawData(reinterpret_cast<char*>(&data), sizeof(PodExample));
    }
    return data;
}

方法3:逐个成员序列化(最安全但代码量大)

cpp

复制代码
QByteArray structToByteArrayExplicit(const PodExample &data) {
    QByteArray byteArray;
    QDataStream stream(&byteArray, QIODevice::WriteOnly);
    stream << data.id << data.value;
    stream.writeRawData(data.name, 32);  // 固定长度字符数组
    return byteArray;
}

PodExample byteArrayToStructExplicit(const QByteArray &byteArray) {
    PodExample data;
    QDataStream stream(byteArray);
    stream >> data.id >> data.value;
    stream.readRawData(data.name, 32);
    return data;
}

使用示例

cpp

复制代码
void exampleUsage() {
    PodExample original;
    original.id = 42;
    original.value = 3.14159;
    strncpy(original.name, "Example", 32);

    // 方法1使用
    QByteArray bytes1 = structToByteArray(original);
    PodExample restored1 = byteArrayToStruct(bytes1);

    // 方法2使用
    QByteArray bytes2 = structToByteArraySafe(original);
    PodExample restored2 = byteArrayToStructSafe(bytes2);

    qDebug() << "Original:" << original.id << original.value << original.name;
    qDebug() << "Restored1:" << restored1.id << restored1.value << restored1.name;
    qDebug() << "Restored2:" << restored2.id << restored2.value << restored2.name;
}

重要注意事项

  1. 字节序问题

    cpp

    复制代码
    // 如果需要跨平台,应处理字节序
    original.id = qToLittleEndian(original.id);  // 转换为小端
  2. 数据校验

    cpp

    复制代码
    // 可添加校验和
    qint16 checksum = qChecksum(bytes1.constData(), bytes1.size());
  3. 调试查看

    cpp

    复制代码
    qDebug() << "Hex dump:" << bytes1.toHex();
  4. 结构体大小验证

    cpp

    复制代码
    static_assert(sizeof(PodExample) == sizeof(int) + sizeof(double) + 32, 
                 "结构体大小不符合预期");