Qt 提供了不少容器类:QVector、QList、QQueue、QSet、QMap、QHash……刚接触时很容易纠结。尤其是 QVector、QQueue、QSet,看起来都能存数据,但实际应用场景完全不同。选错容器,轻则代码别扭,重则性能崩溃。
本文用最直白的对比 + 真实开发案例,带你一次搞懂这三个容器。
三句话背下来
-
需要按下标快速取元素 →
QVector -
需要先进先出排队处理 →
QQueue -
需要快速判断“在不在” →
QSet
一、先看一个选错容器的惨案
某项目需要保存在线设备列表。开发者这样写:
QVector<QString> devices;
devices.append(deviceId); // 上线
devices.removeOne(deviceId); // 下线
if (devices.contains(deviceId)) {} // 判断在线
项目上线半年后,设备从几十台涨到几万台。结果:CPU飙升,界面卡顿,查询越来越慢。
原因很简单:QVector::contains() 每次都要遍历整个数组(O(n)),几万台设备时每次判断都要扫描几万次。这个场景压根不该用 QVector,而该用 QSet。
二、QVector:顺序存储,按下标访问最快
本质:Qt版动态数组,内存连续,类似 std::vector。
最适合的场景:你需要通过下标(索引)快速访问元素。
QVector<int> scores;
scores.append(90);
scores.append(85);
scores.append(95);
int first = scores[0]; // O(1) 极快
int second = scores[1];
实战案例:图像处理软件批量加载图片
QVector<QImage> imageList;
for (int i = 0; i < 1000; ++i) {
imageList.append(loadImage(i));
}
// 随时跳转到第888张
QImage img = imageList[888];
QVector 的缺点:在中间或头部插入很慢(O(n))。
imageList.insert(0, newImage); // 慢!会移动后面所有元素
如果你需要频繁头部插入,改用 QList 或 std::deque。
一句话总结:存一堆数据,主要按下标随机访问 → QVector。
三、QQueue:先进先出,排队专用
本质:基于 QList 实现的队列,FIFO(First In First Out)。
最适合的场景:任务需要按添加顺序一个一个处理。
QQueue<QString> taskQueue;
taskQueue.enqueue("任务1");
taskQueue.enqueue("任务2");
taskQueue.enqueue("任务3");
QString first = taskQueue.dequeue(); // 取到 "任务1"
QString second = taskQueue.dequeue(); // 取到 "任务2"
实战案例1:下载任务调度
用户连续添加多个下载链接,你需要按顺序一个个下载:
class Downloader {
QQueue<QUrl> pendingUrls;
public:
void addUrl(const QUrl &url) { pendingUrls.enqueue(url); }
void onDownloadFinished() {
if (!pendingUrls.isEmpty())
startDownload(pendingUrls.dequeue());
}
};
实战案例2:生产者-消费者模型(多线程)
QQueue<DataPacket> buffer;
QMutex mutex;
// 生产者线程
buffer.enqueue(packet);
// 消费者线程
DataPacket p = buffer.dequeue();
QQueue 的局限:判断元素是否存在效率低(O(n))。
if (taskQueue.contains("任务2")) // 能用,但慢
如果需要频繁查询,建议配合 QSet 使用。
一句话总结:任务需要先来先处理 → QQueue。
四、QSet:快速查重,判存在神器
本质:哈希集合,无序,自动去重。
最适合的场景:核心操作是“判断一个元素在不在集合里”。
QSet<QString> blacklist;
blacklist.insert("192.168.1.100");
blacklist.insert("10.0.0.1");
if (blacklist.contains(ip)) {
// 拒绝连接
}
底层哈希查找,平均复杂度 O(1) —— 无论集合里有几万还是几十万元素,都是一瞬间。
实战案例1:在线用户管理
QSet<QString> onlineUsers;
onlineUsers.insert("user123");
onlineUsers.insert("user456");
// 判断是否在线
if (onlineUsers.contains("user123")) { // 极快
// 转发消息
}
onlineUsers.remove("user456"); // 下线
实战案例2:去重统计
QSet<int> uniqueIds;
for (auto &record : allRecords) {
uniqueIds.insert(record.id);
}
qDebug() << "独立ID数量:" << uniqueIds.size();
QSet 的缺点:
-
无序(遍历结果不是插入顺序)
-
无法通过下标访问
for (auto &user : onlineUsers) {
// 顺序是乱的,不能依赖顺序
}
一句话总结:核心操作是“判断在不在” → QSet。
五、快速对比表
|
你的核心需求 |
推荐容器 |
绝对不要用 |
|---|---|---|
|
通过下标 |
QVector |
QQueue / QSet |
|
按添加顺序取出最早加入的 |
QQueue |
QVector |
|
频繁判断“这个值有没有出现过” |
QSet |
QVector / QQueue |
|
既要按下标访问,又要快速查重 |
QVector + QSet 配合 |
单一容器不够 |
六、混合使用
一个真正的项目往往需要多种容器配合。下面是一个文件同步工具的例子,同时用到三个容器
// 1. 存储所有文件列表(用于显示,按下标访问)
QVector<FileInfo> allFiles;
// 2. 待上传任务队列(按添加顺序上传)
QQueue<FileInfo> uploadQueue;
// 3. 已上传文件路径集合(快速判断是否已上传)
QSet<QString> uploadedPaths;
// 添加新文件时
void addNewFile(const FileInfo &file) {
allFiles.append(file); // 保存到列表
uploadQueue.enqueue(file); // 加入上传队列
}
// 上传完成时
void onFileUploaded(const QString &path) {
uploadedPaths.insert(path); // 记录已上传
}
// 判断是否已上传(极快)
bool isUploaded(const QString &path) {
return uploadedPaths.contains(path);
}
// 获取下一个待上传任务
FileInfo getNextUpload() {
if (!uploadQueue.isEmpty())
return uploadQueue.dequeue();
return FileInfo();
}
这样,QVector 负责存储和下标访问,QQueue 负责排队调度,QSet 负责快速查重,各司其职。
七、总结
|
容器 |
一句话特点 |
典型场景 |
|---|---|---|
| QVector |
动态数组,按下标快 |
图片列表、数据缓存 |
| QQueue |
先进先出队列 |
下载任务、消息队列 |
| QSet |
哈希集合,查重极快 |
在线用户、黑名单、已访问标记 |
下次再纠结时,问自己三个问题:
-
我需要用下标
[i]吗? → 是:QVector -
我需要先来先服务吗? → 是:QQueue
-
我需要判断“在不在”吗? → 是:QSet

1959

被折叠的 条评论
为什么被折叠?



