C++ set/multiset 底层红黑树:内存、迭代器与性能实战剖析
几个月前我在优化模拟项目X的排行榜模块时遇到一个诡异现象内存占用随着在线人数持续增长插入同样数量数据的耗时也不断拉高。一开始我怀疑是业务代码里对象拷贝太狠后来把热点路径上的std::vector std::sort换成了std::multiset插入确实快了但内存问题反而更突出。最后定位才发现是我自己把 multiset 当普通列表在用完全低估了红黑树节点的大小和分配开销。那次折腾让我下定决心把 set 和 multiset 从“会用”变成“懂为什么”这篇就是我复盘后的完整笔记拿出来跟你们聊聊那些不翻标准文档就容易踩碎的细节。1. 从红黑树讲起为什么标准库要选它来做 set 的地基1.1 一次内存暴涨背后的元凶节点不只是数据本身模拟项目X的排行榜每次进来一个玩家就要插入一条记录记录里只有玩家ID和分数两个整数。最初用std::multiset是为了方便按分数排序取前100名但上线后监控显示内存占用比预估翻了快四倍。原因在于std::multiset内部是一棵红黑树每个元素不是一个紧凑的 struct而是一个独立的树节点。节点里除了你存的数据还带三个指针左孩子、右孩子、父节点外加一个颜色位。在64位平台上如果数据是8字节的uint64_t一个节点就是 8*3 1颜色按对齐补到8 8数据 40 字节左右而直接存一个pairint,int只要8字节。四倍差距就是这么来的。你可能会说那用 vector 存结构体不也一样要额外空间吗不一样。vector 是连续内存没有每元素指针开销而且分配器一次性申请一批malloc 的次数少很多。红黑树则是一个元素一次分配malloc 内部的元数据开销通常16字节上下也会摊到每个节点上。所以当你关心内存时set 和 multiset 绝不是“无脑替换”的选项它在时间上的有序性优势是用内存换来的。1.2 为什么选红黑树而不是 AVL 树很多人学数据结构时都知道 AVL 树平衡更严格为什么标准库偏选红黑树关键在“最坏情况下的摊还代价”和“实际插入删除时的旋转次数”。AVL 树要求左右子树高度差不超过1平衡更苛刻查找确实更快一点但插入和删除后触发的旋转频率更高每次旋转还要更新高度信息工程实现复杂。红黑树放宽到“最长路径不超过最短路径的两倍”依然能保证最坏O(log n)的深度但插入后的重平衡最多两次旋转删除后最多三次这个常数对实际性能非常友好。标准库要照顾的是通用场景不是某个极端查询密集、写入几乎为零的程序。红黑树在读写混合场景下的综合表现更稳定节点旋转、颜色翻转的实现也更简单所以历史上一众标准库实现比如某种常见的标准库形式都选了红黑树。理解这一点对使用者的价值在于set 和 multiset 的“有序性”有代价但代价不是无序的——它把写入和查找的复杂度都控制在一个可预测的对数范围内这比 AVL 那种“读多写少才划算”的结构更符合现实业务。1.3 复杂度与真实运行时间别只看大 OO(log n)看着很好但 n 小时未必比线性扫描快。一个std::vector存1000个元素你直接find走一遍现代CPU的分支预测和内存预取可能让线性查找跑得飞快而红黑树的节点分散在堆上每次比较都可能发生缓存未命中。只有当元素量级到几万以上时红黑树对数优势才明显拉开。我自己在模拟项目X里做过一个微基准10万条整数记录随机插入再随机查询。std::multiset的插入总耗时大概比std::vector尾部插入加std::sort慢因为 vector 只需一直 push_back最后排一次序但查询时 multiset 明显胜出因为它不需要每次重新排序。结论是如果数据是“批量到达、排序后长期只读”vector 加排序也许更好如果是“持续插入且随时要顺序访问”set 系容器更合适。后面第5节我会给一份更完整的对比。2. set 和 multiset 接口差异背后是“允许重复”的连锁反应2.1 insert 返回值一个 pair 和一个迭代器的差距std::set::insert返回std::pairiterator, boolbool 表示这次插入是否真的插入了。如果 set 里已有等价元素则不会插入迭代器指向已存在的元素bool 为 false。而std::multiset::insert只返回一个iterator因为它允许重复插入永远成功没有必要用 bool 告诉你“插没插进去”。这个 API 区别很多人知道但没细想过它带来的连锁反应。最直接的影响是当你写一个通用的插入函数想用auto [it, ok] c.insert(x);这种结构化绑定对 multiset 是编译不过的。所以业务代码里如果需要在两个容器间抽象就得小心翼翼。我在模拟项目X里就封装过一个模板函数用if constexpr (std::same_asContainer, std::setT)来区分处理虽然啰嗦但至少编译期干净。另一个容易被忽略的点是set::insert返回的 bool 可以当“去重标志”用但判断“元素是否存在”不应该用 insert 的返回值来试探因为如果元素已经存在你白白构造了一个临时对象并触发了查找。正确做法是先find或containsC20再决定是否插入除非你能接受“尝试插入”的写法。2.2 erase 的三副面孔按值删和按位置删在 multiset 里完全不是一回事erase(key)在 set 里最多删一个元素返回值是0或1在 multiset 里会删掉所有与 key 等价的元素返回值是删除个数。这个差异在业务上很容易引起事故。模拟项目X之前有个清理逻辑从 multiset 中移除某个用户的某条记录结果一个erase(userScore)把所有同分用户记录全删了。因为 multiset 里 key 是分数不是用户ID等价元素一堆。修复方案是用auto it c.find(userScore); if (it ! c.end()) c.erase(it);只删迭代器指向的那一个。但这里还有坑erase(iterator)在 C03 时代返回 voidC11 之后返回指向下一个元素的迭代器。如果你写的是跨 C 标准的兼容代码这个返回值不是总能用的。正确删除一个等价元素的惯用手法是结合lower_bound而不是find因为 find 返回的是任意一个等价元素而 lower_bound 返回第一个不小于 key 的位置二者都能用但 lower_bound 在 multiset 中语义更明确。如果你要用equal_range删掉一个区间里的部分元素注意erase(first, last)会删整个区间返回下一个迭代器。2.3 顺序统计与范围操作lower_bound / upper_bound / equal_range由于 set 和 multiset 有序你天然可以用二分查找来做范围查询。lower_bound(k)返回第一个大于等于 k 的迭代器upper_bound(k)返回第一个大于 k 的迭代器equal_range(k)相当于这两个的组合返回一个pairiterator, iterator。在 set 中[lower_bound(k), upper_bound(k))区间里最多只有一个元素在 multiset 中则包含了所有等于 k 的元素。很多人会问既然 multiset 允许重复那求“某个值有多少个”直接count(k)不就行了技术上可以但count在 multiset 中的实现往往是从 lower_bound 到 upper_bound 线性走一遍来计数。如果同一个值的数量很大比如几十万个重复分数count就是 O(重复个数)。如果你只是想知道“有没有”用contains或find如果你真想知道数量并且重复值很多不如额外维护一个std::unordered_mapKey, size_t做频率表multiset 只负责排序。这是我在模拟项目X里性能优化后的最终结构混合使用比单一容器好用得多。3. 自定义比较器排序规则的边界与正确性红线3.1 严格弱序编译器在背后假设了什么set 和 multiset 并不要求元素类型有operator但要求你提供的比较器满足“严格弱序”strict weak ordering。这个概念听起来很高深拆开就三条对任意元素comp(x, x)必须为 false如果comp(a,b)为 true 则comp(b,a)必须为 false如果comp(a,b)和comp(b,c)都为 true则comp(a,c)也必须为 true。同时“两个元素都不小于对方”被视为等价标准容器会用这种等价关系来确定“是否重复”。很多人忽略的是等价equivalent不等于相等equal。两个对象可能字段不同但只要比较器认为它们既不大于也不小于对方set 就会把它们当成同一个键。比如你把分数作为 key那所有分数相同的用户对象都是“等价”的set 只保留第一个插入的。如果你想按分数排序且允许同分多个人存在就必须用 multiset或者你的比较器里加入用户ID作为次级排序条件。这个决策直接影响容器选型。3.2 常见错误比较器不满足严格弱序的后果最常见的错误是拿operator或operator当比较器。因为comp(x,x)会返回 true这直接违反“非自反”要求。后果不是立刻崩溃而是红黑树在某些插入路径上走错分支导致find找不到刚插入的元素甚至可能导致死循环或越界属于未定义行为。另一个隐蔽的坑来自浮点数如果你用double做 key而数学计算出现了 NaN——比较器对 NaN 的任何比较都返回 false于是 NaN 既不大于任何数也不小于任何数这能容忍但问题在于comp(NaN, NaN)也是 false两个 NaN 会被视为等价吗在严格弱序下这通常是允许的可浮点比较的传递性在 NaN 存在时会被破坏比如comp(a,b)为 false 且comp(b,c)为 false但comp(a,c)可能为 true这时候容器行为就乱了。我的建议是set 的 key 尽量不要用原始浮点把它归一化成一个离散的整数或自定义类型。比较器的第三个高频问题是在比较函数里访问了非静态成员或外部可变状态。红黑树的结构依赖于比较器的稳定输出如果比较器在插入时返回一个顺序、在查找时因为某个外部计数器变化而返回另一个顺序那么整棵树的平衡条件和搜索路径全部失效。标准要求比较器必须在整个容器生命周期内产生一致的顺序。如果需要按“最近活跃时间”动态排序set 不是好选择应该考虑基于链表和哈希索引的结构。3.3 透明比较器与异构查找C14 的隐藏福利C14 给关联容器加了一个很有用的能力透明比较器。只要比较器类型定义里有一个is_transparent类型标记find、lower_bound、count等接口就能接受与 key 不同类型的参数而不必构造一个临时 key 对象。举个例子你需要存一个带有分数和名字的自定义类型按分数排序struct ScoreEntry { int score; std::string name; }; struct Cmp { using is_transparent void; bool operator()(const ScoreEntry a, const ScoreEntry b) const { return a.score b.score; } bool operator()(int score, const ScoreEntry b) const { return score b.score; } bool operator()(const ScoreEntry a, int score) const { return a.score score; } }; std::setScoreEntry, Cmp s; auto it s.find(100); // 不用构造 ScoreEntry如果没有透明比较器你只能在find时构造一个完整的ScoreEntry即使你只关心分数。这对包含长字符串的对象来说是一次不必要的拷贝和内存分配。模拟项目X里的用户表就是用了这个技巧查找时直接传用户ID省掉每次构造临时对象的开销。你还可以用std::less直接少写两个重载但需要注意它会对所有operator都能比较的类型开放容易在类型混用时产生歧义。4. 迭代器失效规则set/multiset 和 vector 最大的不同4.1 插入为什么“基本”不失效vector 插入元素会导致后面的迭代器失效因为要搬移内存set 插入则不会让任何已有迭代器失效只在树里增加一个叶子节点并调整局部指针其他节点的地址不变。这个性质让 set 容器在“一边遍历一边插入”的场景下很安全。但是要注意如果你插入的是一个和已有元素等价的键set 会直接放弃插入这不算迭代器问题但会让新手误以为插入成功了后续操作选了错误的迭代器。红黑树的重平衡虽然会改变节点之间的父子关系但节点本身在内存中的位置没变所以迭代器指向的对象依然有效。你唯一需要担心的是在 multiset 中插入相同 key 的元素标准没有规定等价元素之间的相对顺序但无论顺序怎么变指向已有节点的迭代器仍然有效。不过你别指望“先插入的元素一定排在等价区间的最前面”这种实现细节标准不保证。4.2 erase 返回值的历史沿革与正确删除姿势erase(iterator)的返回值是一个经典演化故事。C03 中设置为void你在遍历时删除当前元素必须先把下一个迭代器备份for (auto it s.begin(); it ! s.end(); ) { if (should_delete(*it)) { s.erase(it); // 先自增再把原迭代器交给 erase } else { it; } }C11 以后erase(iterator)直接返回下一个迭代器更简洁for (auto it s.begin(); it ! s.end(); ) { if (should_delete(*it)) { it s.erase(it); } else { it; } }这里最容易犯的错误是混用两种习惯在 C11 代码里写it s.erase(it);。这个表达式既有旧式的后置自增又把原迭代器赋值给 it结果 it 可能被跳过两个元素。我在代码审查里见过好几次这种写法基本都是从老项目迁移时留下的坏习惯。标准容器删除后原迭代器已经失效再解引用就是未定义行为哪怕它在红黑树实现里碰巧还能用也只是靠运气。4.3 并发读写时的隐藏问题很多人以为 set 的迭代器稳定就意味着可以一边读一边写。不是的。C 标准库容器的线程安全只保证“多个线程同时读”是安全的只要有一个线程在插入或删除所有其他线程的读操作都需要外部加锁否则迭代器可能在读的过程中看到不一致的树结构。红黑树在插入时可能触发颜色翻转和旋转这些操作不是原子的正在遍历的迭代器会看到内存中半修改的节点状态。模拟项目X早期就踩过这个坑一个线程周期性地清理过期元素另一个线程在遍历 multiset 生成榜单。加锁又会造成性能瓶颈后来我们把写操作全部丢到队列里由专门的线程处理后发布一个只读快照遍历线程直接读取快照对象。这不是 set 本身的缺陷而是所有 mutable 容器共有的问题。理解迭代器“只能保证在单线程操作序列里的稳定”才不会在高并发项目里被它背刺。5. 性能实测与场景取舍set 不是万能容器5.1 红黑树 vs 哈希表O(log n) 和 O(1) 的残酷现实很多人觉得unordered_set的均摊 O(1) 一定吊打 set 的 O(log n)实际上数据量小的时候两者差距有限。因为 unordered_set 要计算哈希、处理哈希碰撞、访问桶数组每一步都有固定开销set 只要做几个字符串或整数比较。我在模拟项目X里对 100 万条随机整数做插入再查找的压测unordered_set 的插入确实快约 20%但删除反而更慢因为删除后需要维护 bucket 的状态。红黑树则稳定得多最坏情况也是有序插入所有元素树高度始终控制在约log2(n)。如果你的业务对“最坏延迟”敏感比如游戏服务器里不允许某个玩家操作触发一次 100ms 的哈希表扩容set 这种无扩容、无重哈希的结构更有优势。哈希表在负载因子超过阈值时会重建所有桶这是 O(n) 的操作单次抖动可能很大。红黑树每次操作只涉及一条路径上的节点单次延迟可控。这就是为什么一些实时系统宁愿用 tree 而不是 hash。5.2 内存布局与缓存命中率为什么有时候 vector sort 更快红黑树节点分散分配缓存局部性很差。你在遍历 set 时CPU 需要跳到完全不相邻的内存地址而 vector 的元素是连续存放的遍历时硬件预取器能提前把后面的数据拉进缓存。所以当你的主要操作是“一次性批量插入然后频繁顺序遍历”时维护一个 vector 并在插入后重新排序遍历速度可能比 set 快几倍。这只是适用于“元素数量变化不频繁”或者“你能容忍排序不是实时”的场景。如果元素插入和删除非常频繁每次排序或重新插入的代价就上来了。一个折中方案是“批处理”把新元素先丢进一个小缓冲当缓冲超过阈值时统一插入 set。我的一个经验是排行榜更新频率是每秒几十次每次只插入几个元素但查询是每毫秒都在发生用 set 直接维护顺序是合理的如果每秒有上万次插入先攒一批再用insert的范围接口插入能明显减少树的局部旋转次数。5.3 如何给项目做一个靠谱的基准测试不要拿网上随便找的评测结论决定技术选型。我建议你自己写一个最小压测程序至少模拟三种场景只插入、按 key 随机查找、范围遍历。每种场景下跑 100 万次操作用std::chrono::steady_clock记录耗时并同时用getrusage或/proc/self/status记录峰值内存不要只看时间不看内存。模拟项目X里那次的教训就是我只测了时间没测内存导致上线后内存报警。选容器必须同时考虑时间复杂度和空间占用。另外如果元素对象里有std::string或vector一个完整的节点拷贝会比裸int重得多这时候std::set的插入可能比unordered_set更慢因为红黑树比较时可能要多次访问深层的字符串而哈希表只需要比较一次哈希值。在小结构大对象场景下优先考虑哈希容器或者自定义比较器先比较短键。6. 节点句柄与 merge现代 C 才有的高效重组操作6.1 extract 拿走了节点但不析构C17 引入的extract是关联容器一个被低估的功能。它会从树中摘除一个节点返回一个node_type节点句柄此时元素的所有权和生命周期转移到了句柄中不再属于容器。关键是它不会触发任何分配或释放内存也不会做额外的拷贝或移动因为底层的红黑树节点被整体移交了。std::setint a {1, 2, 3, 4, 5}; auto handle a.extract(3); handle.value() 30; // 修改键值 a.insert(std::move(handle)); // 插回常数时间这有什么用一个典型场景是“重算键值”因为 set 的 key 不可变你不能直接修改已存在元素的键。传统做法是erase再insert这会造成一次分配和释放用extract把节点拿出来改完值再插回去零分配速度快且不会让其他迭代器失效。注意修改 key 后必须仍然满足比较器顺序否则在红黑树中插入位置可能错误产生未定义行为。所以修改前先想清楚这个新值在树中的正确位置是否和原来相同如果不同我通常把extract出来的节点先存着再调用insert让它按规则归位而不是手动塞回。6.2 merge 在容器之间搬运节点merge(container)把另一个容器中的所有节点转移进当前容器。对 set 来说如果目标要插入的元素在源容器中已经存在该节点不会被转移而是留在原容器中对 multiset 来说merge 通常会转移所有节点。这个操作的整体复杂度是N log M因为没有为每个节点分配新内存比遍历插入节省了大量调用。std::setint src {1, 2, 3}; std::setint dst {3, 4}; dst.merge(src); // src 里剩下 {3}dst 变成 {1,2,3,4}在 multiset 之间 merge 时你可能会踩一个坑multiset 允许重复但 merge 不保证 src 中完全清空因为某些节点可能因为比较器类型不同而无法转移实际上标准只说如果容器比较器不同或者分配器不兼容行为由实现定义。所以跨容器 merge 前要确保比较器类型相同否则宁可循环 insert。我在模拟项目X里做数据迁移时就吃过比较器类型不一致导致 merge 后部分数据残留的亏。6.3 multiset 特殊行为与实战心得multiset 和 set 在支持节点句柄后还有一个细节差异multiset 的extract(key)会摘除哪个等价元素标准只说要摘除一个与 key 等价的元素不保证是哪一个。如果你的业务对“删除哪一条”敏感应该在存储时往元素里加一个唯一标识在比较器中把唯一标识作为次级排序条件这样等价区间只有一个元素extract 的语义就明确了。我目前的使用体会是现代 C 里 set 系容器并不是“无条件可用”的工具它适合那些需要有序性、需要范围查询、需要稳定的最坏延迟的场景如果只做去重和存在性判断优先考虑unordered_set如果数据量小且有序性要求低优先考虑vector。在实际项目里最常见的高效组合是 multiset 负责排序unordered_map负责去重加上节点句柄和 merge 做批量数据搬运性能和可维护性都能兼顾。最后给大家一个建议每次选型前先拿你自己的数据规模和操作分布写个三五十行的微基准把这几个容器都跑一遍别让某个在线评测站的数据替你做了决定。

相关新闻

Farming Code 架构解析:Language Server 与 Browser Use 如何支撑 Agent 持续耕作

Farming Code 架构解析:Language Server 与 Browser Use 如何支撑 Agent 持续耕作

1. 从 "Farming Code" 这个名字说起:它到底在解决什么问题第一次看到 "Farming Code" 这个标题,很多人会以为是某种游戏外挂或者自动化脚本合集。但结合 agent、Language Server、Browser Use、Computer Use 这几个关键词来看&#…

2026/10/13 11:47:23 阅读更多 →
CrashData:R语言清洗航空事故数据的完整实践指南

CrashData:R语言清洗航空事故数据的完整实践指南

简介:CrashData 是一份面向数据分析与航空安全研究者的 R 语言数据采集脚本资源,用于从 planecrashinfo.com 逐年抓取飞机失事的历史记录,解决公开事故数据分散、难以批量获取的问题。作者以略显取巧但可用的方式将网页内容整理为规整的 data…

2026/10/13 11:47:23 阅读更多 →
高德地图校园导航Android项目实战:从Key配置到路线规划全解析

高德地图校园导航Android项目实战:从Key配置到路线规划全解析

简介:这份基于高德地图的校园导航资源,是面向在校学生与初学者的完整项目包,适用于课程设计、毕业设计或项目初期演示。包内提供可直接运行的Android工程与APK安装包,涵盖Java源码、XML布局与配置、Gradle构建脚本及多个SO库&…

2026/10/13 11:46:22 阅读更多 →

最新新闻

openGauss数据库实验指南:从环境搭建到JDBC连接全解析

openGauss数据库实验指南:从环境搭建到JDBC连接全解析

简介:一份完整的《zzu数据库实验报告书》,面向计算机科学与技术、软件工程专业正在学习《数据库系统原理》课程的学生,覆盖认识DBMS、创建数据库/表/索引、交互式SQL语句、视图、完整性控制、安全性控制、事务与并发控制、数据库备份与恢复、…

2026/10/13 12:31:31 阅读更多 →
PyCharm安装激活全流程:从下载到稳定使用的避坑指南

PyCharm安装激活全流程:从下载到稳定使用的避坑指南

简介:这份资源是一份面向Python初学者与开发环境搭建新手的PyCharm安装教程文档,重点解决IDE下载、安装配置及许可证激活过程中容易卡壳的问题,适合刚接触Python、需要快速跑通开发环境的学生与转行开发者参考。资源包内共1个docx文档&#x…

2026/10/13 12:31:31 阅读更多 →
微信小程序毕设实战:Spring Boot+MySQL在线阅读系统

微信小程序毕设实战:Spring Boot+MySQL在线阅读系统

简介:这是一套面向计算机专业本科生的微信小程序毕业设计完整实现方案,适用于课程设计、毕设选题与全栈开发能力训练。系统采用JavaMySQLB/S架构,前端为微信小程序,后端提供管理后台,覆盖小说信息维护、用户注册管理及…

2026/10/13 12:31:31 阅读更多 →
Python实现微博自动化:模拟登录、发博与评论的完整指南

Python实现微博自动化:模拟登录、发博与评论的完整指南

简介:自动发布微博并自动回复评论的Python脚本包,面向需要微博自动化运营的学生、站长及开发者,基于HTTP接口实现,适合具备Python基础、想快速搭建个人微博机器人的读者。资源压缩包共4个文件,包含2个Python脚本、1个M…

2026/10/13 12:31:31 阅读更多 →
Java超市管理系统与SQL Server实战:从收银到库存的完整实现

Java超市管理系统与SQL Server实战:从收银到库存的完整实现

简介:这份资源是面向Java初学者与课程设计开发者的超市管理系统项目源码,基于Java语言结合SQL Server数据库实现,适合作为毕业设计、课程作业或企业级入门练手参考。系统覆盖商品管理、库存控制、销售记录、会员管理与报表生成等核心模块&…

2026/10/13 12:31:31 阅读更多 →
SVM短期负荷预测实战:从例程到避坑指南

SVM短期负荷预测实战:从例程到避坑指南

简介:这份例程面向电力系统负荷预测方向的学习者与研究人员,聚焦支持向量机在短期负荷预测中的落地应用。与直接将影响因素送入模型的常规做法不同,它在训练前对各影响因素进行加权预处理,以突出关键特征、削弱弱相关因素干扰&…

2026/10/13 12:30:31 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →