尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
快排O(n log n)的真相:划分、递归深度与期望值三支柱解析
1. 为什么“快排是O(nlogn)”这句话90%的人根本没真正听懂你肯定在算法课、面试题、技术博客里反复见过这个说法“快速排序平均时间复杂度是O(n log n)”。它像一句咒语被背得滚瓜烂熟写在简历上挂在嘴边甚至出现在LeetCode题解的开头。但如果你停下来问自己一句这个O(n log n)到底是怎么算出来的它成立的前提是什么如果我写的快排在实际数据上跑出了O(n²)问题到底出在哪一层——很多人会突然卡住。这不是记不住公式的问题而是对“复杂度”本身的理解停留在标签层面。O(n log n)不是快排的出厂设定而是一个在特定假设下、经过严格数学推导得出的期望值结论。它背后藏着递归树的结构、划分的均衡性、随机化的作用以及最常被忽略的一点这个“平均”指的是对所有可能输入排列的等概率平均而不是你本地测试用的那几组数据的平均。我第一次真正搞懂这个公式是在给某高校算法实训课带学生调试时。一个学生写了标准快排用完全有序数组测试耗时直接爆炸——他脱口而出“老师快排不是O(n log n)吗怎么我这跑得比冒泡还慢”那一刻我意识到我们教算法常常把结论当起点却把推导过程当可跳过的附录。而真正的工程能力恰恰藏在“为什么结论不成立”的排查链路里。所以这篇内容不讲怎么写快排网上代码一抓一大把也不堆砌证明过程那属于《算法导论》的使命。我要带你回到那个最原始的场景当你面对一个真实数组按下运行键CPU开始执行内存里发生着什么每一层递归调用栈里比较和交换究竟在做什么划分操作的微小偏差如何在递归深度上被指数级放大我们要拆开这个黑盒看清楚O(n log n)这个数字究竟是从哪一行伪代码、哪一个分支判断、哪一次pivot选择中生长出来的。核心关键词就三个划分Partition、递归深度Recursion Depth、期望值Expectation。它们像三根支柱撑起了整个O(n log n)的结论。接下来的所有分析都将围绕这三者展开——不是抽象地谈理论而是结合具体数据、具体代码、具体内存行为告诉你这个复杂度到底是“算”出来的还是“测”出来的抑或是“撞”出来的。2. 划分操作快排性能的真正命门远不止“找中位数”那么简单快排的骨架非常简洁选一个基准pivot把数组分成三部分——小于pivot、等于pivot、大于pivot然后递归处理左右两部分。这个“分”的动作就是划分Partition。教科书上常把它简化为一个while循环两指针相向而行。但正是这个看似简单的操作埋下了所有性能差异的种子。2.1 划分的三种经典实现及其隐藏代价市面上最常见的划分实现有三种它们在时间、空间、稳定性上各有取舍但对复杂度的影响却截然不同Lomuto划分单指针扫描伪代码核心逻辑维护一个i指针指向已处理区的右边界遍历j从左到右每遇到arr[j] pivot就将arr[j]与arr[i1]交换并i。最后把pivot放到i1位置。优点逻辑极其清晰代码短教学友好。致命缺陷每次划分都必须遍历整个子数组且交换次数不可控。当pivot选得极差比如总是最小值它会产生一个长度为n-1的子问题和一个空子问题递归树退化成链表深度变为O(n)总比较次数飙升至O(n²)。更隐蔽的是它对重复元素极其敏感——大量相等元素会导致频繁无意义交换实测下来在含50%重复值的数组上比Hoare划分慢30%以上。Hoare划分双指针相向伪代码核心left从左向右找≥pivot的元素right从右向左找≤pivot的元素两者相遇即停交换后继续。pivot通常放在最左或最右划分后pivot位置不确定需额外处理。优点交换次数显著减少对重复元素鲁棒性强。因为它是“跳过”相等元素而非逐个交换。在大量重复数据场景如日志时间戳排序这是性能关键。代价逻辑稍复杂边界条件易错比如left right的判断时机初学者容易写出死循环。三路划分Dutch National Flag伪代码核心维护三个指针lt(less than),gt(greater than),i(current)将数组分为pivot、pivot、pivot三段。i遍历时根据arr[i]与pivot关系决定是交换到lt区、gt区还是直接i。优点彻底解决重复元素导致的性能坍塌问题。当数组中存在大量重复值时它能将等于pivot的部分直接隔离后续递归只处理和两部分规模大幅缩减。Java 7的Arrays.sort()对基本类型就采用此策略。代价代码最长理解成本最高且在无重复数据时额外指针操作带来微小开销约5%。提示不要迷信“教科书实现”。我在某跨平台图像处理Demo中曾因使用Lomuto划分处理传感器采集的灰度直方图大量重复像素值导致排序模块成为整个流水线瓶颈。切换到三路划分后耗时从800ms降至120ms——这不是理论优化是真实数据下的血泪教训。2.2 Pivot选择那个被低估的“第一刀”划分的效率70%取决于pivot选得是否合理。选得好数组被近乎等分递归树平衡选得差一边肥大一边瘦弱树高陡增。常见的pivot策略有固定位置法首/尾/中代码最简但极易被构造数据击穿。例如若永远选第一个元素输入已是升序则每次划分产生n-1和0的子问题直接O(n²)。这是面试官最爱的陷阱题。随机化法Randomized Pivot在[low, high]范围内随机选一个索引将其与low位置交换再执行划分。这是理论保证O(n log n)期望时间的核心。其数学本质是对任意输入随机选择pivot使得划分后左右子数组大小的期望比例为1:1从而保证递归树期望深度为log₂n。实测中它对恶意数据如已排序、逆序、锯齿形有极强免疫力。三数取中法Median-of-Three取low、high、(lowhigh)/2三个位置的值取其中位数作为pivot。它不依赖随机数生成器在嵌入式等资源受限环境更可靠。但仍有被针对性构造数据绕过的可能如“三数取中对抗序列”。注意随机化不是万能药。我曾在一个实时音视频处理项目中因在中断服务程序ISR里调用rand()导致系统抖动。后来改用预生成的随机索引数组轮询既保持了随机性又规避了系统调用开销。工程选择永远是理论、环境、约束的三角博弈。2.3 划分操作的微观世界一次比较一次交换都在做什么让我们聚焦一次具体的划分过程。假设数组为[3, 6, 8, 2, 1, 9, 4, 5]选pivot5取中位数用Hoare划分left0(3),right7(5) →35,55→left→left1(6)65,55→ 交换arr[1]和arr[7]→[3, 5, 8, 2, 1, 9, 4, 6]left2(8),right6(4) →85,45→ 交换 →[3, 5, 4, 2, 1, 9, 8, 6]left3(2),right5(9) →25,95→left,right--→left4(1),right4→ 相遇停止。最终leftright4pivot5应置于arr[4]。此时数组被划分为[3,5,4,2,1]≤5和[9,8,6]≥5但注意5并未在最终位置它还在arr[1]。这就是Hoare划分的特点它只保证[low, left]全≤pivot[right, high]全≥pivotpivot本身需单独放置。这个过程揭示了关键事实划分不是在“排序”而是在“聚类”。它不关心[3,5,4,2,1]内部顺序只确保它们都≤5。这种“局部有序全局未定”的状态正是递归能持续进行的基础。每一次划分都是在为下一层递归划定更小的、性质明确的战场。3. 递归深度O(n log n)的“log n”从何而来一棵树的生死簿如果说划分操作定义了快排的“宽度”那么递归深度就决定了它的“高度”。O(n log n)中的log n正是源于递归树的期望深度。但这个深度绝非固定不变它像一根绷紧的弦随pivot选择的优劣剧烈波动。3.1 递归树的构建从一维数组到二维结构想象一下对一个长度为n的数组执行快排。第一层递归我们选pivot划分出两个子数组长度分别为k和n-k-1减1是pivot本身。第二层对这两个子数组各自再选pivot、划分……如此递归下去直到子数组长度为1或0。这个过程天然形成一棵二叉树根节点代表原始数组规模为n。每个内部节点代表一次划分操作其值为该次划分的pivot。每个节点的左右子节点分别代表划分后产生的“小于pivot”和“大于pivot”的子数组。叶子节点代表长度为1或0的子数组递归终止。这棵树的深度就是从根到最深叶子的路径长度对应着最坏情况下的递归调用栈深度。而整棵树的所有节点的规模之和就是所有划分操作中参与比较的元素总数——这正是时间复杂度的物理意义。3.2 理想与现实平衡树 vs 链状树理想情况完美平衡每次pivot都恰好是中位数子数组长度均为n/2。此时递归树是一棵满二叉树。第0层1个节点规模n第1层2个节点各规模n/2总规模n第2层4个节点各规模n/4总规模n……每一层的总规模恒为n。树高为log₂n层数为log₂n 1。因此总比较次数 ≈n * log₂n即O(n log n)。最坏情况完全失衡每次pivot都是最小或最大值子数组长度为n-1和0。此时递归树退化为一条链。第0层规模n第1层规模n-1第2层规模n-2……第n-1层规模1。总比较次数 n (n-1) (n-2) ... 1 n(n1)/2即O(n²)。平均情况随机化保障这是理论分析的精华所在。我们不假设pivot每次都完美而是计算在所有n!种输入排列中随机选择pivot递归树的期望深度是多少答案是≈ 1.39 log₂n。推导基于一个关键观察一个元素x要成为另一个元素y的祖先即在y被划分前x就被选为pivot并将其分到不同子树当且仅当x和y在原始数组的相对顺序中x是y左侧或右侧第一个满足|x-y|最小的元素。这个概率是2/|i-j|。对所有元素对求和再积分最终得到期望比较次数为~ 1.39 n log₂n。提示这个1.39常数不是凭空来的。它来自调和级数H_n 1 1/2 1/3 ... 1/n ≈ ln n γγ为欧拉常数而ln n log₂n * ln 21/ln 2 ≈ 1.4427再乘以一个修正因子就得到了1.39。它提醒我们O(n log n)不仅是阶更是一个有具体系数的、可估算的量。在需要精确性能建模的场景如高频交易系统这个系数至关重要。3.3 递归深度的工程实测你的栈够深吗理论深度是log₂n但实际运行中栈深度受编译器、操作系统、语言运行时影响巨大。我曾在某嵌入式设备上测试对100万整数排序理论深度约20层但实际ulimit -s显示栈大小仅8MB而每次递归调用栈帧约200字节含参数、返回地址、局部变量20层仅4KB完全无压力。然而当误用Lomuto划分处理极端数据时栈深度瞬间飙到10万层直接触发Segmentation Fault。这引出了一个关键工程实践尾递归优化Tail Recursion Optimization, TRO。观察快排递归结构划分后我们通常先递归左子数组再递归右子数组。但第二个递归调用右子数组其实是尾调用——它之后没有其他操作。编译器如GCC开启-O2或语言如Scala可将其优化为循环避免压栈。更激进的做法是手动实现始终对较小的子数组进行递归较大的子数组用循环处理。这样能将最坏情况下的栈空间复杂度从O(n)降至O(log n)。// 手动尾递归优化的快排核心 void quickSortOptimized(int arr[], int low, int high) { while (low high) { int p partition(arr, low, high); // 标准划分 // 优先递归处理较小的子数组避免栈溢出 if (p - low high - p) { quickSortOptimized(arr, low, p - 1); low p 1; // 循环处理右半部分 } else { quickSortOptimized(arr, p 1, high); high p - 1; // 循环处理左半部分 } } }这段代码的精妙在于它用一个while循环替代了第二个递归调用将“深度优先”变成了“深度优先循环展开”。实测表明在处理1000万数据时它比朴素递归版本节省了99%的栈空间且速度提升约8%因为避免了函数调用开销。4. 期望值的真相O(n log n)不是“大概率发生”而是“所有可能的平均”这是最容易被误解的一点。“平均时间复杂度O(n log n)”中的“平均”不是指“你多测几次取个平均值”而是严格的数学期望值Expected Value。它建立在概率空间上所有可能的输入排列构成一个样本空间每个排列出现的概率相等1/n!而快排在每个排列上的运行时间是一个随机变量T(π)。O(n log n)指的是E[T(π)] Θ(n log n)。4.1 期望值 vs 概率为什么“99%概率O(n log n)”还不够好一个常见误区是认为“只要pivot选得足够随机就有99%的概率得到O(n log n)”。这其实不够严谨。期望值的威力在于它允许存在极小概率的坏事件如连续选到最差pivot只要这些坏事件的贡献被其极低的概率所抵消期望值依然可以很漂亮。举个直观例子假设一个算法有99.999%的概率在1毫秒内完成但有0.001%的概率需要1000秒。它的期望运行时间 0.99999 * 0.001 0.00001 * 1000 ≈ 10.001秒。虽然“大概率很快”但期望值已经崩坏。而快排的精妙之处在于其坏事件如深度为n的概率衰减得极快指数级以至于即使坏事件耗时O(n²)其对期望值的贡献也仅为O(n log n)。这个结论的证明依赖于马尔可夫不等式Markovs Inequality对于非负随机变量XP(X ≥ a) ≤ E[X]/a。令X为递归深度D则P(D ≥ c log n) ≤ E[D]/(c log n) ≤ (1.39 log₂n)/(c log n)。选择足够大的c如c10这个概率就小于1/n²。这意味着在n次独立运行中深度超过10 log n的概率小于1/n几乎可以忽略。4.2 实测中的“期望”如何设计一个有意义的性能测试既然理论期望值如此抽象工程师该如何验证自己的快排实现是否真的达到了O(n log n)关键在于测试数据的构造必须覆盖概率空间。错误做法只用几组手工构造的数据如[1,2,3,...,n]、[n,n-1,...,1]、[1,1,1,...,1]。这只能测出最坏情况无法反映“平均”。正确做法三步走随机数据集生成大量如1000组长度为n的随机排列。对每组运行快排记录耗时。计算平均耗时T_avg(n)。理论上T_avg(n)应与n log n成正比。拟合曲线对不同n如n1000, 2000, ..., 100000计算T_avg(n)然后用最小二乘法拟合T a * n * log₂n b。若拟合优度R² 0.99且系数a稳定则验证通过。压力测试专门构造“难缠”数据如近似有序[1,2,3,...,n-100, random(100), n-99,...,n]多峰分布[1..n/3, n..2n/3, n/31..n/3100]模拟日志中不同业务模块的时间戳混合重复块[1,1,1,...,1 (1000个), 2,2,2,...,2 (1000个), ...]我在为某数据分析平台做快排模块验收时就采用了这套方法。结果发现一个号称“优化版”的库函数在随机数据上表现优异a1.42但在“多峰分布”数据上a飙升至5.8——原因是其pivot策略对局部聚集不敏感。这直接否决了该方案转而采用三路划分随机化组合。4.3 期望值的边界当“平均”不再适用时期望值是一个强大的工具但它有其适用边界。在以下场景中“O(n log n)期望”可能失去指导意义实时系统Real-time Systems如自动驾驶的感知模块要求任何一次排序必须在1ms内完成。此时最坏情况O(n²)比期望O(n log n)重要一万倍。解决方案是放弃快排选用最坏情况O(n log n)的堆排序或归并排序或使用基数排序若数据范围有限。在线算法Online Algorithms数据流式到达无法预知总量。快排需要随机访问不适用。此时应考虑自适应排序如Timsort它能利用数据中已有的有序片段。外部排序External Sorting数据远超内存需读写磁盘。快排的随机访问模式导致大量I/O远不如归并排序的顺序读写高效。经验在某物联网网关固件开发中我们曾试图用快排处理传感器缓存约5000条记录。理论很美但实测发现由于Flash存储的擦写延迟随机访问的I/O耗时波动极大导致整体响应时间抖动严重。最终改用归并排序的变种将I/O模式规整为顺序读写抖动降低了90%。算法选择永远是问题域、数据特征、硬件约束的联合解。5. 从理论到落地一个工业级快排实现的完整拆解现在让我们把前面所有的洞见组装成一个真正可用于生产环境的快排实现。它不追求极致的学术优雅而是平衡了性能、鲁棒性、可读性和可维护性。5.1 核心设计决策与理由决策项选项选择理由实测影响划分策略三路划分Dutch Flag彻底解决重复元素性能坍塌现代主流库Java, Python已验证其普适性在含30%重复数据的场景比Lomuto快2.1倍Pivot策略随机化 三数取中后备随机化保证理论期望三数取中作为fallback避免rand()系统调用开销在恶意构造数据下最坏耗时降低99.7%递归优化尾递归优化 小数组切换尾递归保栈安全小数组n10切插入排序消除小规模递归开销对10000元素数组减少函数调用2300次提速12%内存模型原地排序不分配额外数组符合嵌入式及内存敏感场景需求空间复杂度O(log n)内存占用恒定无GC压力5.2 完整C语言实现含详细注释#include stdlib.h #include time.h #include stdio.h // 辅助函数交换两个整数 static inline void swap(int *a, int *b) { int t *a; *a *b; *b t; } // 三数取中返回low, mid, high三位置的中位数索引 static int medianOfThree(int arr[], int low, int high) { int mid low (high - low) / 2; // 将三个数排序使arr[low] arr[mid] arr[high] if (arr[mid] arr[low]) swap(arr[low], arr[mid]); if (arr[high] arr[low]) swap(arr[low], arr[high]); if (arr[high] arr[mid]) swap(arr[mid], arr[high]); // 此时arr[mid]是中位数将其与arr[low]交换为划分做准备 swap(arr[low], arr[mid]); return low; // 返回pivot索引现在在low位置 } // 三路划分返回lt和gt使得 [low, lt) pivot, [lt, gt] pivot, (gt, high] pivot static void threeWayPartition(int arr[], int low, int high, int *lt, int *gt) { int pivot arr[low]; *lt low; *gt high; int i low 1; while (i *gt) { if (arr[i] pivot) { swap(arr[*lt], arr[i]); (*lt); i; } else if (arr[i] pivot) { swap(arr[i], arr[*gt]); (*gt)--; // i不递增因为从右边换过来的arr[i]未知需重新检查 } else { i; } } // 此时[low, *lt) 是 pivot, [*lt, *gt] 是 pivot, (*gt, high] 是 pivot } // 插入排序用于小数组优化 static void insertionSort(int arr[], int low, int high) { for (int i low 1; i high; i) { int key arr[i]; int j i - 1; while (j low arr[j] key) { arr[j 1] arr[j]; j--; } arr[j 1] key; } } // 主排序函数带尾递归优化 void quickSort(int arr[], int low, int high) { // 小数组优化长度 10用插入排序 if (high - low 1 10) { insertionSort(arr, low, high); return; } // 随机化pivot在[low, high]内随机选一个索引与low交换 // 使用预生成随机数数组可避免rand()开销此处为简洁用rand() int randomIndex low rand() % (high - low 1); swap(arr[low], arr[randomIndex]); // 备份若随机化失败极小概率用三数取中兜底 // 实际工程中可在此处加一个简单检查如arr[low]是否为极值 // medianOfThree(arr, low, high); // 可选启用 int lt, gt; threeWayPartition(arr, low, high, lt, gt); // 尾递归优化先递归处理较小的子数组较大的用循环 if (lt - low high - gt) { quickSort(arr, low, lt - 1); low gt 1; // 循环处理右半部分 } else { quickSort(arr, gt 1, high); high lt - 1; // 循环处理左半部分 } } // 入口函数封装初始化 void sortArray(int arr[], int n) { if (n 1) return; // 初始化随机数种子实际工程中应在main中一次初始化 srand(time(NULL)); quickSort(arr, 0, n - 1); }5.3 关键细节与避坑指南srand()的位置代码中sortArray里调用srand()是教学简化。真实项目中必须在main()函数入口处调用一次srand(time(NULL))。否则如果sortArray被高频调用如每秒1000次time(NULL)返回值相同导致rand()序列重复随机化失效pivot选择退化为固定模式。三路划分的i不递增陷阱在arr[i] pivot分支中swap后i不自增这是核心。因为从*gt位置换过来的arr[i]其值未知必须立即检查。我曾在一个音频FFT频谱排序模块中因漏掉这行i--错误地写了i导致无限循环。调试时打印i, lt, gt的值才揪出这个魔鬼细节。小数组阈值的选择10不是魔法数字。它取决于CPU缓存行大小通常64字节、指令流水线深度、以及插入排序的常数因子。在x86-64上经实测8-16是最佳区间。阈值过小插入排序调用开销占比高过大则小规模递归的函数调用开销累积。建议在目标平台上用perf工具测量L1 cache miss率来校准。内存对齐警告此实现对int数组有效。若需泛型C11的_Generic或C模板是更好选择。强行用void*和memcpy会引入额外开销且破坏CPU的SIMD向量化潜力。6. 快排之外当O(n log n)不再是终点时快排是算法史上的丰碑但技术演进从未停止。理解快排最终是为了知道何时该放下它。6.1 现代替代方案Timsort与IntrosortTimsortPython, Java 7Arrays.sort(Object[])由Tim Peters为Python设计。它是一种自适应、稳定、混合排序算法。核心思想是现实数据往往包含大量局部有序片段runs。Timsort先扫描数组识别这些自然run然后用归并排序合并它们。其最坏时间复杂度O(n log n)但对部分有序数据可达到O(n)。在Web日志分析场景它比快排快40%因为日志时间戳天然具有时间局部性。IntrosortC STLstd::sort由David Musser提出。它以快排为主干但监控递归深度。一旦深度超过2 log₂n就切换到堆排序。这完美兼顾了快排的平均性能和堆排序的最坏保证是“理论与工程妥协”的典范。其名字introspective自省的即源于此深度监控机制。6.2 超越比较非比较排序的降维打击当数据具备特殊结构时O(n log n)的下界可以被打破计数排序Counting Sort适用于整数且范围[0, k]已知且k O(n)。时间复杂度O(nk)空间O(k)。在游戏排行榜分数排序分数0-10000中它是绝对王者。基数排序Radix Sort将整数按位如byte分组进行多轮计数排序。时间复杂度O(d*(nk))d为位数。对32位整数d4按bytek256故O(n)。在数据库索引构建中它常被底层引擎采用。桶排序Bucket Sort将数据均匀散列到多个桶中桶内用快排或插入排序最后合并。期望时间O(n)但依赖数据均匀分布假设。在地理坐标经纬度聚类排序中效果显著。6.3 我的个人体会算法工程师的思维升级写完这篇我翻出十年前自己写的第一个快排那时只求“能跑通”。如今再看它像一个未经打磨的璞玉——有潜力但布满毛刺。真正的成长不在于记住O(n log n)这个符号而在于看到一个O(n log n)的结论能立刻反向推演出它的假设前提随机输入比较模型内存模型面对一个性能问题能像侦探一样沿着“划分-递归深度-期望值”的链条逐层下钻直到定位到那一行有问题的swap或i。明白所有“最优”都是有条件的快排的“快”是相对于通用比较排序而言Timsort的“稳”是相对于现实数据的局部有序性而言Introsort的“可靠”是相对于递归深度监控的阈值而言。算法不是静态的知识点而是一套动态的、可迁移的思维范式。它教会我们的是如何在不确定性中寻找确定性在约束中创造可能性。下次当你再看到“O(n log n)”时希望你脑海中浮现的不再是教科书上的公式而是内存中跳动的指针、递归栈里层层嵌套的low和high以及那个在partition函数里默默决定一切命运的pivot。这才是算法的血与肉。
RELATED

相关推荐

2026移动开发选型指南:Android、iOS、跨平台与安卓上架全流程

2026移动开发选型指南:Android、iOS、跨平台与安卓上架全流程

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

📅 2026/10/23 3:51:38
ADSL宽带接入:频段划分、DMT调制与线路排障实战

ADSL宽带接入:频段划分、DMT调制与线路排障实战

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

📅 2026/10/23 3:46:37
VirtualBox安装Ubuntu全流程:参数配置、增强功能与网络排错

VirtualBox安装Ubuntu全流程:参数配置、增强功能与网络排错

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

📅 2026/10/23 3:46:37
MORE NEWS

更多资讯

📰

文本块(Text Blocks):告别繁琐的字符串拼接(Java代码实现)

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

📰

Record类:简洁的数据载体(Java代码实战)

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

📰

JDK17内存管理:堆内存与非堆内存优化

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

📰

做私域自动化必看:微信 API 接口高频调用时的防封与限流处理策略

调微信 API 调得猛了,两种情况会让你半夜起来救火:接口返回频率限制,或者账号被风控临时冻住。两种都是"触发了阈值",但处理思路不一样——限流是接口层面的硬限制,防封是账号层面的行为合理性。这篇把两层放…

📰

密封类(Sealed Classes):控制类的继承关系(附代码示例)

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

📰

上下文在烧钱?用asm stats --tokens算清每个技能的token成本,找回AI代理注意力预算

【免费下载链接】asm The universal skill manager for AI coding agents. 项目地址: https://gitcode.com/gh_mirrors/asm17/asm 点击查看 免费下载 asm(agent-skill-manager) 是面向 AI 编码代理的通用技能管理器,它的一条核心…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬