数据结构选型:数组 vs 链表 vs 跳表 vs 红黑树

订单簿的核心,说白了就是一张价格-数量的映射表。但怎么存这张表,直接决定了你的撮合引擎能扛住多大的压力。我见过不少团队,一开始图省事用数组,结果一到行情爆发就崩。今天咱们把这四种结构掰开揉碎,看看在订单簿场景下,谁才是真正的王者。

一、数组:简单但致命

数组的优点是啥?连续内存,CPU缓存友好,随机访问O(1)。听起来很美对吧?

但订单簿不是静态的。价格档位会不断新增、删除、修改。数组的插入和删除是O(n)的——你想想看,每次撤单都要把后面所有元素往前挪,这谁受得了?

避坑指南:我曾经在一个高频项目里看到有人用数组存Level 2数据,结果每秒5000笔撤单时,CPU直接飙到90%。后来换成跳表,CPU降到15%。数组在订单簿里,只适合做快照缓存,不适合做核心结构。

数组唯一能用的地方,是价格档位固定且稀疏度极低的情况。比如某些期货合约只有几十个活跃档位,用数组+位图标记,勉强能跑。但别指望它扛大并发。

二、链表:插入快,查询慢

链表插入是O(1)的,这很诱人。但问题是,订单簿需要按价格排序。链表要找到插入位置,得从头遍历——O(n)。

我个人的习惯是,链表只用在内存池里做空闲节点管理。真正的价格队列,别用链表做主结构。为什么?因为订单簿的查询频率远高于修改频率。你每秒钟可能查几千次最优买卖价,但修改可能只有几百次。链表每次查最优价都得遍历,这太亏了。

小技巧:如果你非要用链表,可以考虑维护一个指向头尾的指针,这样查最优买卖价是O(1)。但中间价格的查询依然慢。说白了,链表只适合做辅助结构。

三、跳表:工程上的最优解

跳表是我在订单簿项目里用得最多的结构。它本质上是多层链表,用概率平衡代替了红黑树的严格平衡。插入、删除、查询都是O(log n),而且实现简单,不容易出bug。

我记得有一次做期权做市系统,订单簿需要支持按价格区间批量查询。红黑树要实现区间遍历得写递归,跳表直接沿着底层链表走就行,代码量少了一半。

// 跳表节点示例
struct SkipListNode {
    double price;           // 价格
    uint64_t volume;        // 总量
    int order_count;        // 订单数
    std::vector<SkipListNode*> forward;  // 各层指针
    
    SkipListNode(double p, int level) 
        : price(p), volume(0), order_count(0), forward(level + 1, nullptr) {}
};

跳表还有个好处:无锁化改造相对容易。我做过一个无锁跳表版本,用CAS操作管理指针,在40核机器上做到了单线程10倍吞吐。红黑树想做无锁?那难度直接翻十倍。

核心结论:跳表在订单簿场景下,是工程复杂度与性能的最佳平衡点。如果你只能选一种结构,我建议选跳表。

四、红黑树:理论最优,工程噩梦

红黑树插入删除也是O(log n),而且比跳表更稳定——跳表有概率退化,红黑树不会。但问题在于:

  • 实现复杂。你能手写一个无bug的红黑树吗?我反正不能。
  • 内存局部性差。每个节点分散在堆里,CPU缓存命中率低。
  • 并发改造难。旋转操作涉及多个指针,CAS很难搞定。

我见过一个团队用std::map(红黑树)做订单簿,结果性能瓶颈不在算法复杂度,而在内存分配。每次插入都new一个节点,每次删除都delete,内存碎片化严重。后来换成跳表+内存池,性能直接翻倍。

红黑树唯一不可替代的场景,是需要严格保证最坏情况延迟的场合。比如某些交易所的风控系统,要求每次操作都在1微秒内完成,这时候红黑树的确定性比跳表的概率性更安全。

五、四种结构对比总结

特性 数组 链表 跳表 红黑树
插入 O(n) O(1)(已知位置) O(log n) O(log n)
删除 O(n) O(1)(已知位置) O(log n) O(log n)
查询 O(1)(随机) O(n) O(log n) O(log n)
区间遍历 O(k) O(k) O(log n + k) O(log n + k)
实现难度
并发友好
缓存友好

六、我的选型建议

嗯,这里直接给结论:

  • 做原型验证:用std::map(红黑树),最快出活。
  • 做生产系统:用跳表+内存池,性能与可维护性兼顾。
  • 做极低延迟系统:用数组+位图(如果价格范围固定),或者定制化跳表。
  • 做风控系统:用红黑树,保证最坏情况延迟。

我个人习惯是,先拿跳表跑起来,等性能瓶颈明确了再考虑优化。别一开始就搞红黑树,调试起来太痛苦了。

避坑指南:我曾经在一个项目里,为了追求理论最优,用了红黑树+自旋锁。结果上线第一天就死锁了——红黑树的旋转操作和锁的粒度没配合好。后来换成跳表+无锁队列,再也没出过问题。有时候,工程上的「足够好」比理论上的「最优」更重要。

七、知识体系结构图

订单簿数据结构选型决策树 订单簿数据结构 简单/固定场景 复杂/动态场景 数组 + 位图 链表(辅助结构) 跳表(推荐) 红黑树(风控场景) 选型核心原则 查询频率 > 修改频率 → 跳表 最坏延迟敏感 → 红黑树

这张图总结了我的选型逻辑。从左到右,从简单到复杂。记住一点:没有银弹。每种结构都有它的适用场景,关键是你得清楚自己的业务特征。


公众号:蓝海资料掘金营,微信deep3321