QByteArray 详解:既是二进制字节数组,也能拥有类 QString 的文本 API
- 1、引言:一个被低估的类
- 2、 QByteArray 是什么
- 2.1 、基本定位
- 2.2、 与 QString 的关系
- 3、创建与初始化
- 4、 二进制视角:真正的字节数组
- 4.1、 追加与访问原始字节
- 4.2、 与数值互转
- 4.3 、十六进制与 Base64
- 5、 文本视角:类 QString 的 API
- 5.1 、查找与判断
- 5.2 、截取、切分与拼接
- 5.3 、修改与替换
- 5.4 、与 QString 的转换
- 6、隐式共享与性能
- 7、常见坑与最佳实践
- 7.1 、多字节编码下的"按字节"陷阱
- 7.2 `、\0` 与 C 字符串的边界
- 7.3、 大小写转换只对 ASCII 有效
- 7.4 、最佳实践清单
- 8、实战:解析一个简单的二进制协议
- 9、 总结
1、引言:一个被低估的类
在 Qt 的世界里,QString几乎是每个开发者最熟悉的类,而QByteArray常常被当成"存二进制数据的容器"随手使用。但如果你翻一翻 Qt 的源码就会发现,QByteArray和QString在 API 设计上高度对称:它们都继承自同一套隐式共享(copy-on-write)机制,都提供了append、prepend、insert、remove、replace、indexOf、split、trimmed、toUpper等一整套文本操作接口。
换句话说,QByteArray是一个"双面派":
- 一面是二进制字节数组:可以存图片、协议包、加密结果,任意字节都能放,包括
\0; - 另一面是类 QString 的文本 API:可以像操作字符串一样做查找、替换、切分、大小写转换。
本文就从这两面出发,把QByteArray的用法、内部机制和常见坑一次讲清楚。
2、 QByteArray 是什么
2.1 、基本定位
QByteArray是 Qt 提供的字节数组类,内部维护一段连续的char数据,并在末尾额外保存一个'\0'终止符(方便直接传给 C 风格 API)。它的核心特征:
- 存储单位是字节,不是字符,因此不涉及编码转换;
- 支持任意字节,包括
0x00; - 隐式共享,拷贝是浅拷贝,写时才真正复制;
- 末尾保证有
'\0',constData()返回的指针可以直接当 C 字符串用。
2.2、 与 QString 的关系
| 维度 | QString | QByteArray |
|---|---|---|
| 存储单位 | UTF-16 码元 | 单字节 |
| 是否含编码信息 | 是(Unicode) | 否(裸字节) |
能否存\0 | 可以 | 可以 |
| 文本 API | 完整 | 基本对称 |
| 典型用途 | 界面文本、国际化 | 网络包、文件内容、编码转换中间态 |
关键区别在于:QString知道自己存的是"字符",而QByteArray只知道存的是"字节"。所以QByteArray的文本 API 本质上是按字节操作的,这一点在涉及多字节编码(如 UTF-8 中文)时必须格外小心。
3、创建与初始化
#include<QByteArray>#include<QDebug>intmain(){// 1. 空构造QByteArray a1;// 2. 从 C 字符串构造(遇到 '\0' 停止)QByteArraya2("hello");// 3. 指定长度构造(可包含 '\0')QByteArraya3("hello\0world",11);// 4. 填充构造:10 个 'x'QByteArraya4(10,'x');// 5. 从 std::string 构造std::string s="from std::string";QByteArray a5=QByteArray::fromStdString(s);// 6. 数字转字节数组QByteArray a6=QByteArray::number(255);// "255"QByteArray a7=QByteArray::number(255,16);// "ff"qDebug()<<a2<<a3.size()<<a4<<a5<<a6<<a7;return0;}注意第 3 种写法:QByteArray("hello\0world", 11)会完整保留中间的\0,而QByteArray("hello\0world")只会得到"hello"。这是二进制场景下最容易踩的坑之一。
4、 二进制视角:真正的字节数组
4.1、 追加与访问原始字节
QByteArray packet;packet.append(char(0x01));// 版本号packet.append(char(0x00));// 保留位packet.append(char(0x10));// 长度packet.append("payload",7);// 7 字节负载// 逐字节访问for(inti=0;i<packet.size();++i){quint8 b=static_cast<quint8>(packet.at(i));qDebug("byte[%d] = 0x%02X",i,b);}// 直接拿裸指针交给 C APIconstchar*raw=packet.constData();qDebug()<<"size ="<<packet.size();at()返回char,在需要无符号语义时务必显式转换,否则0x80以上会被解释成负数。
4.2、 与数值互转
// 整数 -> 字节数组(大端)QByteArray be;be.append(char((value>>24)&0xFF));be.append(char((value>>16)&0xFF));be.append(char((value>>8)&0xFF));be.append(char(value&0xFF));// 字节数组 -> 整数quint32 v=(quint8(be[0])<<24)|(quint8(be[1])<<16)|(quint8(be[2])<<8)|quint8(be[3]);Qt 也提供了qToBigEndian/qFromBigEndian(<QtEndian>)来简化这类操作,手写时注意统一字节序。
4.3 、十六进制与 Base64
QByteArray data=QByteArray::fromHex("48656c6c6f");// "Hello"QByteArray hex=data.toHex();// "48656c6c6f"QByteArray b64=data.toBase64();// "SGVsbG8="QByteArray back=QByteArray::fromBase64(b64);// "Hello"这两个转换在调试协议、处理 JWT、传输二进制附件时非常常用。
5、 文本视角:类 QString 的 API
5.1 、查找与判断
QByteArray text="Content-Type: application/json";qDebug()<<text.contains("json");// trueqDebug()<<text.startsWith("Content");// trueqDebug()<<text.endsWith("json");// trueqDebug()<<text.indexOf(':');// 12qDebug()<<text.lastIndexOf('/');// 245.2 、截取、切分与拼接
QByteArray line=" key=value ";qDebug()<<line.trimmed();// "key=value"qDebug()<<line.mid(2,3);// "key"qDebug()<<line.left(4);// " ke"qDebug()<<line.right(4);// "ue "QByteArray csv="a,b,c";QList<QByteArray>parts=csv.split(',');for(constQByteArray&p:parts){qDebug()<<p;}QByteArray joined=parts.join('|');// "a|b|c"5.3 、修改与替换
QByteArray s="Hello World";s.replace("World","Qt");// "Hello Qt"s.insert(5,",");// "Hello, Qt"s.remove(5,1);// "Hello Qt"s.prepend(">> ");// ">> Hello Qt"s.append(" <<");// ">> Hello Qt <<"s=s.toUpper();// ">> HELLO QT <<"s=s.toLower();// ">> hello qt <<"可以看到,这些接口与QString几乎一一对应,迁移成本极低。
5.4 、与 QString 的转换
QString str="你好,Qt";// QString -> QByteArray(UTF-8)QByteArray utf8=str.toUtf8();// QByteArray -> QString(必须指定编码)QString back=QString::fromUtf8(utf8);// 本地编码(依赖系统区域设置,跨平台慎用)QByteArray local=str.toLocal8Bit();QString fromLocal=QString::fromLocal8Bit(local);这是最容易出问题的地方:QByteArray本身不带编码信息,QString::fromUtf8和QString::fromLatin1对同一段字节会得到完全不同的结果。转换时一定要明确"这段字节是什么编码"。
6、隐式共享与性能
QByteArray采用隐式共享(copy-on-write):
QByteArray a="large data ...";QByteArray b=a;// 浅拷贝,引用计数 +1,不复制数据b.append("x");// 此时才真正深拷贝,a 不受影响这意味着:
- 按值传参、返回
QByteArray的代价很低; - 但非 const 的
operator[]或data()会触发 detach,在循环里频繁调用可能造成意外拷贝。
// 不推荐:每次 data() 都可能 detachfor(inti=0;i<arr.size();++i){arr.data()[i]='x';}// 推荐:先取一次可写指针char*p=arr.data();for(inti=0;i<arr.size();++i){p[i]='x';}另外,reserve()可以预分配容量,避免多次扩容:
QByteArray buf;buf.reserve(1024*1024);7、常见坑与最佳实践
7.1 、多字节编码下的"按字节"陷阱
QByteArray utf8=QString("中文").toUtf8();// 6 字节qDebug()<<utf8.size();// 6,不是 2qDebug()<<utf8.left(1);// 只截到半个汉字,非法 UTF-8left、mid、at都是按字节操作,对 UTF-8 中文做截断会破坏编码。需要按字符处理时,先转成QString再操作。
7.2、\0与 C 字符串的边界
QByteArraybin("A\0B",3);qDebug()<<bin.size();// 3qDebug()<<strlen(bin.constData());// 1,C 函数在 '\0' 处停止把QByteArray传给只接受 C 字符串的函数时,中间含\0的部分会丢失。二进制数据应始终配合长度参数使用。
7.3、 大小写转换只对 ASCII 有效
toUpper()/toLower()只处理 ASCII 字母,对非 ASCII 字节不做任何转换。需要 Unicode 大小写规则时,请使用QString。
7.4 、最佳实践清单
- 二进制数据一律用带长度的构造/追加方式,避免
\0截断; - 文本数据明确编码,转换时优先
toUtf8()/fromUtf8(); - 大块数据拼接前先
reserve(); - 循环内避免反复调用非 const 的
data(); - 需要字符级操作时,先转
QString。
8、实战:解析一个简单的二进制协议
假设协议格式为:[2 字节魔数][2 字节长度][负载],负载为 UTF-8 文本。
#include<QByteArray>#include<QDebug>#include<QtEndian>structPacket{quint16 magic;QByteArray payload;};QByteArraybuildPacket(constQByteArray&text){QByteArray out;out.reserve(4+text.size());quint16 magic=0x5154;// "QT"quint16 len=static_cast<quint16>(text.size());charbuf[2];qToBigEndian(magic,buf);out.append(buf,2);qToBigEndian(len,buf);out.append(buf,2);out.append(text);returnout;}boolparsePacket(constQByteArray&data,Packet&pkt){if(data.size()<4)returnfalse;pkt.magic=qFromBigEndian<quint16>(reinterpret_cast<constuchar*>(data.constData()));quint16 len=qFromBigEndian<quint16>(reinterpret_cast<constuchar*>(data.constData()+2));if(data.size()<4+len)returnfalse;pkt.payload=data.mid(4,len);returntrue;}intmain(){QByteArray raw=buildPacket(QString("你好,QByteArray").toUtf8());qDebug()<<"raw size ="<<raw.size();qDebug()<<"hex ="<<raw.toHex();Packet pkt;if(parsePacket(raw,pkt)){qDebug()<<"magic ="<<Qt::hex<<pkt.magic;qDebug()<<"payload ="<<QString::fromUtf8(pkt.payload);}return0;}这个例子同时用到了二进制视角(qToBigEndian、append(buf, n))和文本视角(toUtf8、fromUtf8),正好体现QByteArray的双重身份。
9、 总结
QByteArray并不是"只能装二进制"的容器,也不是"简化版 QString"。它的准确定位是:
- 底层是字节数组,能安全承载任意二进制数据;
- 上层提供与 QString 对称的文本 API,让字符串式操作同样顺手;
- 两者之间的桥梁是编码,一旦涉及文本,就必须明确字节与字符的边界。
理解这一点,就能在协议解析、文件读写、编码转换等场景中,既享受QString式的便利,又不掉进"按字节处理多字节字符"的陷阱。