从内存到磁盘:2-3树、B树与B+树插入原理及数据库索引设计
1. 从内存到磁盘为什么需要重新理解数据的存储原理很多人学数据结构学到树这一章就开始犯迷糊。数组、链表还能在纸上画一画到了2-3树、B树、B树脑子里就只剩下一堆圆圈和箭头根本想不明白这些结构到底在解决什么问题。我刚开始接触这块内容的时候也一样教材上把插入过程画得清清楚楚但合上书就忘因为我不知道它为什么长这样。后来我才意识到问题的根源在于大多数人跳过了“数据到底存在哪里”这个前置知识。你如果不理解数据在计算机里的存储方式就永远理解不了为什么需要B树这种“多路平衡查找树”。所以这篇内容我打算换个讲法不从树的定义开始而是从数据存储的底层逻辑切入把2-3树的插入原理讲透再自然过渡到B树和B树。这样你学完之后不仅能画出插入过程还能说清楚“为什么数据库索引偏偏选了B树”。这篇内容适合正在学数据结构的大学生、准备面试的开发者以及工作中需要用到数据库索引优化但一直没搞懂底层原理的工程师。我会尽量用生活化的类比来解释同时保证技术细节的准确性。你不需要有很深的算法基础只要知道数组和链表是怎么回事就能跟上。2. 数据在计算机中的两种归宿内存与磁盘的存储逻辑2.1 内存的物理结构决定了数组和链表的命运计算机的内存可以想象成一排长长的信箱每个信箱有一个编号也就是内存地址。CPU要取数据必须先知道数据在哪个信箱里。这个“按地址直接访问”的特性决定了内存是一种随机存取的介质——访问第1个字节和访问第100万个字节耗时几乎一样。数组之所以在内存里效率极高就是因为它是一块连续的内存空间。假设一个int占4个字节数组的首地址是1000那么第5个元素就在10004×41016的位置。CPU用一条指令就能算出地址并取到数据这就是O(1)随机访问的物理基础。但数组有个致命问题插入和删除要搬移元素。你在第3个位置插入一个数据后面所有元素都得往后挪一格。如果数组很大这个搬移成本就很高。链表解决了搬移问题因为它的节点可以散落在内存各处靠指针串联。但链表失去了随机访问能力你要找第100个节点就得从头一个一个跳过去。这里有个容易被忽略的点数组和链表的优劣本质上是由内存的物理特性决定的不是人为设计出来的偏好。理解了这一点你才能理解后面树结构为什么要那样设计。2.2 磁盘的物理结构才是B树诞生的真正原因内存虽好但容量有限且断电就丢。大量数据必须存在磁盘上。磁盘的物理结构和内存完全不同——它是由盘片、磁头、磁道、扇区组成的机械结构。读写数据时磁头要移动到指定磁道盘片旋转到指定扇区这个过程涉及寻道时间和旋转延迟通常以毫秒计比内存访问慢了五个数量级左右。关键来了磁盘每次读写的最小单位不是1个字节而是一个块block通常是4KB或8KB。也就是说你哪怕只想读1个字节磁盘也会把整个块读出来。这个特性彻底改变了数据结构的设计思路。在内存里我们关心的是“比较次数”在磁盘上我们关心的是“磁盘I/O次数”。因为一次磁盘I/O的时间足够CPU做几十万次比较。所以如果一种树结构能让树的高度尽可能低每次节点存储尽可能多的关键字就能大幅减少磁盘访问次数。这就是B树和B树的核心设计动机。我用一个具体的数字让你感受一下假设有10亿条记录用二叉平衡树存储树高约30层每次查找要30次磁盘I/O。如果用1000阶的B树树高只有3层3次磁盘I/O就够了。差距是10倍。对于数据库这种每秒要处理成千上万次查询的系统这个差距是致命的。2.3 从二叉查找树到多路查找树的思维跃迁普通的二叉查找树每个节点最多两个子节点存一个关键字。它的查找效率取决于树高。理想情况下树高是log₂N但普通二叉查找树在极端情况下会退化成链表树高变成N。后来出现了平衡二叉树AVL树、红黑树通过旋转操作把树高控制在log₂N。但即便平衡二叉树把树高控制得很好在磁盘场景下依然不够。因为每个节点只存一个关键字、最多两个子节点导致树高还是太大。要降低树高最直接的办法就是让每个节点存更多关键字、有更多子节点。这就是“多路查找树”的思路。2-3树是最简单的多路查找树每个节点可以存1个或2个关键字有2个或3个子节点。它保留了平衡二叉树的“平衡”思想但突破了“二叉”的限制。理解了2-3树再去看B树和B树就会发现它们只是把“2-3”推广到了“M阶”核心逻辑一脉相承。3. 2-3树的插入原理从“裂开”到“长高”的完整过程3.1 2-3树的基本定义与节点类型2-3树有两种节点2-节点包含1个关键字和2个子节点。左子树的所有关键字小于该关键字右子树的所有关键字大于该关键字。3-节点包含2个关键字和3个子节点。左子树小于第一个关键字中间子树介于两个关键字之间右子树大于第二个关键字。所有叶子节点都在同一层这是2-3树保持平衡的关键。2-3树是一棵绝对平衡的树不像红黑树是“近似平衡”。它的平衡不是靠旋转维持的而是靠节点分裂和向上生长来维持的。我一开始觉得“所有叶子在同一层”这个要求很苛刻后来才明白正是这个约束保证了查找路径长度一致不会出现某条路径特别长的情况。3.2 插入的核心操作先找位置再看要不要裂2-3树的插入分两步走第一步查找插入位置。从根节点开始比较关键字一路找到合适的叶子节点。这个过程和二叉查找树类似只是每个节点可能要比较两次。第二步在叶子节点中插入然后判断是否需要调整。这里分几种情况情况一插入到2-节点中直接变成3-节点。这是最简单的情况。比如一个2-节点存了关键字5你要插入3它直接变成3-节点存{3,5}不需要任何调整。情况二插入到3-节点中节点“裂开”。3-节点已经满了存了2个关键字再插入一个就变成3个关键字违反了2-3树的定义。这时候要把这个节点分裂把中间的关键字向上提左右两个关键字各自成为新的2-节点。情况三分裂后父节点也满了继续向上裂。如果中间关键字提升到父节点后父节点也变成了3个关键字那就继续分裂直到根节点。如果根节点也裂了树就长高一层。3.3 一个完整的插入序列演示我用序列{8, 5, 12, 3, 7, 10, 15, 1}来完整演示一遍。插入8树为空8成为根节点是一个2-节点。插入558插入到8所在的节点变成3-节点{5,8}。插入12128但根节点已经是3-节点了不能再直接插入。这时候需要分裂。{5,8,12}的中间关键字是8把8提上去成为新的根节点5和12分别成为左右子节点。树长高一层。插入338走左子树到5所在的节点。5是2-节点直接变成3-节点{3,5}。插入778走左子树。75但5所在的节点已经是3-节点{3,5}了。插入7后变成{3,5,7}中间关键字5向上提。父节点8是2-节点接收5后变成3-节点{5,8}。原来的{3,5,7}分裂成3和7两个2-节点。插入10108走右子树到12。12是2-节点变成3-节点{10,12}。插入151512但{10,12}已经是3-节点。插入15后变成{10,12,15}中间关键字12向上提。父节点{5,8}是3-节点接收12后变成{5,8,12}又满了。继续分裂中间关键字8向上提成为新的根节点。5和12分别成为左右子节点。树又长高一层。插入118走左子树到5。15走左子树到3。3是2-节点变成3-节点{1,3}。整个过程下来你会发现2-3树的生长方向是从下往上的而且每次长高都是因为根节点分裂。这和二叉查找树从叶子往下生长的方式完全不同。这里有个容易搞混的地方分裂时“中间关键字向上提”提上去之后原来的节点变成两个节点这两个节点成为父节点的新子节点。父节点接收了中间关键字后子节点数量也要相应调整。这个细节在画图时特别容易出错。3.4 为什么2-3树能保持绝对平衡2-3树保持平衡的秘诀在于所有叶子节点都在同一层而且树只能从根节点向上生长。每次插入都是在叶子节点进行分裂产生的提升会一层层向上传递最终要么在某个非根节点被吸收要么一直传到根节点导致树长高。这意味着树高的增加是“全局性”的——要么所有叶子都长高一层要么都不变。不会出现某条路径比另一条路径长的情况。这就是绝对平衡的含义。对比一下红黑树红黑树是近似平衡左右子树高度差不超过一倍。2-3树是绝对平衡所有叶子在同一层。2-3树在概念上更清晰但实现起来更复杂因为节点类型不统一2-节点和3-节点结构不同。红黑树可以看作是2-3树的一种“二叉化”实现用颜色标记来模拟3-节点。4. B树把2-3树推广到M阶的工程化方案4.1 B树的定义与阶的概念B树是2-3树的推广。2-3树每个节点最多2个关键字、3个子节点B树则允许每个节点有M-1个关键字和M个子节点M就是阶。一棵M阶B树满足以下条件每个节点最多有M个子节点。每个节点最多有M-1个关键字。根节点至少有2个子节点除非它是叶子。除根节点外每个非叶子节点至少有⌈M/2⌉个子节点。所有叶子节点在同一层。节点内的关键字按升序排列。“至少有⌈M/2⌉个子节点”这个约束很重要它保证了B树不会退化成稀疏的树维持了查找效率。如果允许节点只有1个子节点那树就可能变得又高又瘦失去多路查找的优势。4.2 B树的插入分裂策略与2-3树一脉相承B树的插入逻辑和2-3树完全一致只是“满了”的判断标准从“2个关键字”变成了“M-1个关键字”。具体步骤从根节点开始查找插入位置一路找到叶子节点。在叶子节点中按序插入关键字。如果节点关键字数量超过M-1进行分裂取中间位置的关键字向上提升到父节点左右两部分各自成为新节点。如果父节点也溢出继续向上分裂直到根节点。如果根节点溢出创建一个新根节点树高增加一层。这里的关键参数是中间位置的选择。对于M阶B树节点最多M-1个关键字溢出时有M个关键字。中间位置通常是第⌈M/2⌉个关键字从1开始计数。这个选择保证了分裂后左右两个节点都至少有⌈M/2⌉-1个关键字满足B树的最小关键字约束。我举个例子5阶B树节点最多4个关键字。插入后变成5个关键字比如{10,20,30,40,50}。中间位置是第3个即30。30向上提左边{10,20}右边{40,50}各2个关键字满足5阶B树非根节点至少2个关键字的要求。4.3 B树与2-3树的关键差异虽然插入逻辑一脉相承但B树在工程实现上有几个重要差异第一阶数可以很大。2-3树固定为3阶B树的M可以是几十甚至几百。数据库中的B树索引M通常取100以上这样树高可以压到3-4层。第二节点大小与磁盘块对齐。实际实现中B树的一个节点通常设计为正好一个磁盘块的大小如4KB。这样一次磁盘I/O就能读入一个完整节点最大化利用每次I/O。节点内关键字数量根据关键字大小和磁盘块大小计算得出。第三删除操作更复杂。2-3树的删除涉及“借关键字”和“合并节点”B树的删除逻辑类似但情况更多。不过面试中问得最多的是插入删除相对少一些。第四B树的关键字和记录可以存在一起。在B树中每个节点既存关键字也存实际数据或数据指针。这意味着查找可能在非叶子节点就命中不一定非要走到叶子。这是B树和B树的一个重要区别。4.4 B树在文件系统中的应用逻辑B树在文件系统和一些数据库中被用作索引结构。它的优势在于查找路径短且每个节点存储密集能充分利用磁盘块的容量。但B树有个问题因为关键字和数据存在一起每个节点能存的关键字数量就少了。如果数据很大节点能容纳的关键字就更少树高就会增加。另外B树的查找性能不稳定——最好的情况在根节点就找到最坏的情况要走到叶子虽然差距不大但在某些对性能稳定性要求极高的场景下这是个考虑因素。这些问题催生了B树。5. B树数据库索引的终极选择及其设计智慧5.1 B树对B树做了哪三件事B树在B树的基础上做了三个关键改动改动一数据只存在叶子节点。非叶子节点只存关键字不存实际数据。这样每个非叶子节点能容纳更多关键字树高进一步降低。改动二叶子节点之间用指针串联成链表。所有叶子节点按关键字顺序链接形成一个有序链表。这个改动直接支持了范围查询。改动三非叶子节点的关键字是叶子节点关键字的“副本”。非叶子节点中的关键字只是用来导航的实际数据都在叶子。查找时一定要走到叶子才能拿到数据。这三个改动看起来简单但带来的效果是革命性的。5.2 为什么数据库索引偏爱B树数据库查询有两种典型模式等值查询找某个特定值和范围查询找某个区间内的所有值。对于等值查询B树和B树都能胜任。B树可能在非叶子节点就命中看起来更快。但B树因为非叶子节点不存数据每个节点能存更多关键字树高更低总体I/O次数可能更少。对于范围查询B树的优势是碾压性的。因为叶子节点串成了链表找到区间的起点后顺着链表往后读就行了不需要回到上层节点。B树要做到这一点必须反复从根节点往下查找效率低得多。还有一个工程上的原因B树的查询性能更稳定。因为所有数据都在叶子每次查找都必须走到叶子路径长度一致。B树则可能在不同层级命中性能有波动。对于数据库这种要求可预测性能的系统稳定性很重要。我用一个实际场景来说明假设你要查“所有年龄在25到30岁之间的用户”。B树找到25后顺着叶子链表读到30就结束了。B树则要对每个年龄值单独查找每次都要从根节点走一遍。数据量越大差距越明显。5.3 B树的插入与分裂和B树有什么不同B树的插入逻辑和B树类似但分裂时的处理有差异。在B树中当叶子节点溢出时分裂后中间关键字会被复制到父节点而不是像B树那样移动到父节点。原来的关键字仍然保留在叶子节点中。这是因为B树的所有关键字都必须在叶子节点出现。举个例子3阶B树叶子节点最多2个关键字。插入后变成3个比如{10,20,30}。分裂时中间关键字20被复制到父节点叶子节点分成{10,20}和{30}两个节点。注意20同时存在于父节点和左叶子节点中。如果分裂发生在非叶子节点处理方式和B树一样中间关键字向上移动不保留在原节点。因为非叶子节点的关键字只是导航用的副本不需要保留。这个差异在画图时特别容易搞混。我的经验是记住“叶子复制非叶子移动”这个口诀基本不会出错。5.4 聚簇索引与非聚簇索引B树的两种落地形态在数据库实现中B树索引分为聚簇索引和非聚簇索引。聚簇索引的叶子节点直接存储完整的数据行。找到索引就找到了数据。一张表只能有一个聚簇索引通常建在主键上。非聚簇索引的叶子节点存储的是主键值不是完整数据行。找到索引后还需要根据主键值去聚簇索引中再查一次这个过程叫“回表”。回表是有成本的。如果查询的列都在索引中就不需要回表这叫“覆盖索引”。在实际优化中覆盖索引是提升查询性能的重要手段。理解这两种索引的区别对于写出高效的SQL至关重要。比如为什么主键推荐用自增整数因为自增主键插入时总是在B树最右侧追加不会导致节点分裂和页分裂写入性能最好。如果用UUID做主键插入位置随机会导致频繁的页分裂和碎片性能下降明显。6. 从理论到代码手写一个简化版B树插入逻辑6.1 节点结构设计与关键参数我用Python写一个简化版的B树重点展示插入和分裂逻辑。为了便于理解这里不考虑磁盘I/O只关注算法本身。class BPlusTreeNode: def __init__(self, is_leafFalse): self.is_leaf is_leaf self.keys [] self.children [] # 非叶子节点存储子节点指针 self.next None # 叶子节点的链表指针 self.parent None class BPlusTree: def __init__(self, order4): self.order order # M阶 self.root BPlusTreeNode(is_leafTrue) self.max_keys order - 1 self.min_keys (order 1) // 2 - 1这里order就是阶M。max_keys是节点最多能存的关键字数量。min_keys是非根节点至少需要的关键字数量计算方式是⌈M/2⌉-1。6.2 插入与分裂的核心代码def insert(self, key): leaf self._find_leaf(key) self._insert_into_leaf(leaf, key) def _find_leaf(self, key): node self.root while not node.is_leaf: i 0 while i len(node.keys) and key node.keys[i]: i 1 node node.children[i] return node def _insert_into_leaf(self, leaf, key): if key in leaf.keys: return # 简化处理不允许重复 leaf.keys.append(key) leaf.keys.sort() if len(leaf.keys) self.max_keys: self._split_leaf(leaf) def _split_leaf(self, leaf): mid len(leaf.keys) // 2 new_leaf BPlusTreeNode(is_leafTrue) new_leaf.keys leaf.keys[mid:] leaf.keys leaf.keys[:mid] new_leaf.next leaf.next leaf.next new_leaf new_leaf.parent leaf.parent self._insert_into_parent(leaf, new_leaf.keys[0], new_leaf) def _insert_into_parent(self, left, key, right): parent left.parent if parent is None: new_root BPlusTreeNode(is_leafFalse) new_root.keys [key] new_root.children [left, right] left.parent new_root right.parent new_root self.root new_root return idx parent.children.index(left) parent.keys.insert(idx, key) parent.children.insert(idx 1, right) right.parent parent if len(parent.keys) self.max_keys: self._split_internal(parent) def _split_internal(self, node): mid len(node.keys) // 2 up_key node.keys[mid] new_node BPlusTreeNode(is_leafFalse) new_node.keys node.keys[mid 1:] node.keys node.keys[:mid] new_node.children node.children[mid 1:] node.children node.children[:mid 1] for child in new_node.children: child.parent new_node new_node.parent node.parent self._insert_into_parent(node, up_key, new_node)这段代码的核心逻辑和前面讲的完全对应叶子分裂时中间关键字复制到父节点非叶子分裂时中间关键字移动到父节点。_insert_into_parent处理了根节点分裂导致树长高的情况。6.3 实测中的边界情况与调试技巧实际跑这段代码时有几个地方容易出问题第一叶子分裂时mid的计算。如果叶子有3个关键字mid1左叶子保留1个右叶子2个。如果叶子有4个关键字mid2左右各2个。这个分配要保证两边都不少于min_keys。对于4阶B树min_keys1所以怎么分都满足。但如果阶数更大就要仔细验证。第二父节点插入位置的计算。parent.children.index(left)找到左子节点在父节点中的位置然后在对应位置插入关键字和右子节点。这个索引关系必须准确否则树的结构就乱了。第三根节点分裂的处理。根节点没有父节点分裂时要创建新根。新根的关键字是提升上来的中间关键字两个子节点分别是原来的根和分裂出的新节点。调试时我建议用一个小的阶数比如3或4手动插入一组数据然后打印整棵树的结构和纸上画的结果对比。这样能快速定位逻辑错误。7. 面试与实战中的高频问题梳理7.1 为什么B树比B树更适合做数据库索引这个问题几乎每场后端面试都会问。回答的核心要点有三个第一B树的非叶子节点不存数据同样大小的磁盘块能存更多关键字树高更低磁盘I/O次数更少。第二B树的叶子节点串成链表范围查询效率极高。B树做范围查询需要反复从根节点查找。第三B树查询性能稳定每次都要走到叶子路径长度一致。B树可能在不同层级命中性能有波动。如果面试官继续追问“那B树有什么场景比B树好”可以回答B树适合那些经常访问离根节点较近的数据的场景因为可能在非叶子节点就命中减少一次I/O。但在数据库这种范围查询频繁的场景B树的优势更大。7.2 为什么MySQL的InnoDB选择B树而不是跳表或哈希跳表在内存数据库如Redis中很常见因为跳表实现简单范围查询也支持。但在磁盘场景下跳表的树高还是太大磁盘I/O次数多。哈希索引只支持等值查询不支持范围查询而且哈希冲突处理在磁盘上代价高。B树在磁盘场景下是综合最优解树高低、支持范围查询、性能稳定、节点大小可与磁盘块对齐。7.3 实际工作中B树索引的优化经验我在实际项目中有几条经验第一主键尽量用自增整数。自增主键插入时总是在B树最右侧追加不会触发中间节点的分裂写入性能最好。UUID做主键会导致随机插入频繁页分裂性能和存储空间都受影响。第二合理使用覆盖索引。如果查询的列都在索引中就不需要回表性能提升明显。比如经常执行SELECT name FROM users WHERE age 25可以建一个(age, name)的联合索引这样查询直接在索引中完成。第三注意联合索引的最左前缀原则。联合索引(a, b, c)能支持a、(a,b)、(a,b,c)的查询但不支持b、(b,c)的查询。这是因为B树是按索引列的顺序排列的跳过最左列就无法利用有序性。第四索引不是越多越好。每个索引都是一棵B树占用存储空间且插入、更新、删除时都要维护索引影响写入性能。一般建议单表索引不超过5-6个。7.4 从2-3树到B树的知识串联回顾整条线索内存的随机存取特性决定了数组和链表的优劣磁盘的块读写特性催生了多路查找树2-3树是最简单的多路查找树用节点分裂和向上生长保持绝对平衡B树把2-3树推广到M阶适配磁盘块大小B树在B树基础上把数据全部下沉到叶子并串联链表成为数据库索引的标准结构。这条线索上的每一个设计决策背后都有明确的工程动机。理解了这些动机你就不需要死记硬背插入步骤而是能自己推导出来。我在学习这块内容时最大的体会就是不要孤立地看每种树要把它们放在“数据存储在哪里”这个背景下理解。一旦想通了这层关系2-3树、B树、B树就不再是三棵独立的树而是同一套思想在不同场景下的演进。最后分享一个我常用的学习方法每学一种新树就问自己三个问题——它解决什么问题它为此付出了什么代价如果换一种设计会怎样这三个问题能帮你把知识点串成线而不是散落成一地碎片。

相关新闻

form-create 富文本组件 @form-create/component-wangeditor 接入指南与源码解析

form-create 富文本组件 @form-create/component-wangeditor 接入指南与源码解析

低代码前端UI组件 【免费下载链接】form-create :fire::fire::fire: 强大的低代码动态表单组件,通过 JSON 数据驱动表单渲染,适配移动端,支持可视化设计。提高开发者对表单的开发效率。目前在政务系统、OA系统、ERP系统、电商系统、流程管理等…

2026/10/15 14:50:28 阅读更多 →
从体验到旗舰:什么信号说明该加量

从体验到旗舰:什么信号说明该加量

体验档跑通后,常有人问要不要加量。信号不在焦虑,而在产能是否真吃紧:未发队列是否排满、授权号是否已齐、素材是否周周有、现有日更是否仍吃不完。 市面加购常按篇加价;加量前若还在同站连发,先停手。风控没消&#x…

2026/10/15 14:50:28 阅读更多 →
n8n自动化工作流实战:Grist与Hacker News节点应用

n8n自动化工作流实战:Grist与Hacker News节点应用

1. 项目背景与核心价值最近在搭建自动化工作流时,发现n8n这个开源工具在数据处理和流程编排方面有着惊人的灵活性。特别是当我们需要将不同平台的数据打通时,n8n的节点系统就像乐高积木一样可以自由组合。这次要重点分享的是两个特别实用的节点&#xff…

2026/10/15 14:50:28 阅读更多 →

最新新闻

时间轮定时器原理与实战:单轮、多轮实现及避坑指南

时间轮定时器原理与实战:单轮、多轮实现及避坑指南

简介:本资源是一套Java时间轮定时器的完整实现代码,面向具备一定Java基础、希望深入理解高效定时任务调度机制的开发者。内容围绕单轮与多轮两种时间轮结构展开,涵盖槽位划分、指针轮询、任务插入与移除等核心环节,可用于心跳检测…

2026/10/15 15:36:28 阅读更多 →
Arduino BLE FTMS 实战:让老健身车接入 Zwift 等训练 App

Arduino BLE FTMS 实战:让老健身车接入 Zwift 等训练 App

简介:这份资源面向嵌入式开发与室内骑行训练爱好者,提供一套基于Arduino与ESP32的BLE FTMS室内自行车健身机实现方案,用两块ESP32分别模拟功率计与智能骑行台,解决商用骑行台价格高昂、协议封闭的问题。压缩包共30个文件&#xff…

2026/10/15 15:36:28 阅读更多 →
Gradpaper AI 初稿生成实测:项目信息输入质量对论文产出效果的影响研究

Gradpaper AI 初稿生成实测:项目信息输入质量对论文产出效果的影响研究

AI 论文写作工具越来越多,很多学生误以为工具可以凭空生成高质量论文初稿。但在 Gradpaper 实测过程中发现,初稿质量最大的决定因素不是模型能力,而是项目信息卡的填写完整度。本文设置多组对照实验,量化不同项目信息输入条件下&a…

2026/10/15 15:36:28 阅读更多 →
搜索插入位置:二分查找边界条件与lowerBound实现详解

搜索插入位置:二分查找边界条件与lowerBound实现详解

1. 题目场景与核心思路拆解LeetCode热题100里的“搜索插入位置”这道题,我刷了不下三遍,每一遍都有新体会。题目本身不长:给定一个排序数组和一个目标值,在数组中找到目标值并返回下标,如果不存在,则返回它…

2026/10/15 15:36:28 阅读更多 →
STM32串口屏配置数据丢失?cJSON移植与解析实战

STM32串口屏配置数据丢失?cJSON移植与解析实战

简介:cJson库是专为嵌入式系统打造的轻量级JSON解析与生成工具,面向STM32等单片机开发者,解决资源受限环境下JSON数据交互的难题。压缩包内共2个文件,包含1个cJSON.h头文件与1个cJSON.c实现文件,整体仅9KB,…

2026/10/15 15:36:28 阅读更多 →
C++17实现简单工厂模式:现代C++设计模式实战指南

C++17实现简单工厂模式:现代C++设计模式实战指南

《大话设计模式》在程序员圈子的地位不用多说,很多人第一次接触“面向对象设计”“开闭原则”“依赖倒置”,都是从它的计算器例子开始的。但真轮到自己动手,把书里的案例用 C 重写一遍时,几乎每个人都会卡在同一个地方&#xff1a…

2026/10/15 15:35:28 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →