Java 8 DoubleSummaryStatistics 实现员工工资统计与最高工资人数
后端开发里统计员工工资这种需求太常见了——人数、工资总和、平均工资、最高、最低挨个算。Java 8的DoubleSummaryStatistics就是专门为这类汇总统计准备的工具类配合Stream一行就能把五个核心指标同时算出来。但实际接需求时往往不止这五个数比如领导经常会问一句拿最高工资的有几个人这个DoubleSummaryStatistics偏偏不直接给。这篇文章就拿员工薪资统计这个场景把类的用法、边界情况和最高工资人数的几种实现一起讲透给你一套能直接抄的代码。1. DoubleSummaryStatistics能给你什么五个指标一次遍历全拿到1.1 底层原理为什么summaryStatistics能一遍过DoubleSummaryStatistics是java.util包下的小工具类从Java 8开始随Stream API一起出现。它的核心设计思路很朴素维护count、sum、min、max、average这几个内部状态每接收一个double值就同步更新一次。你把它当成一个带状态的累加器就对了。通常我们不会直接new这个类而是通过Stream的链式调用让它自动工作DoubleSummaryStatistics stats employees.stream() .mapToDouble(Employee::getSalary) .summaryStatistics();这里有个关键点用的是mapToDouble而不是map。原因在于summaryStatistics()只在DoubleStream、IntStream、LongStream这几个原始类型流上提供普通对象流是不行的。mapToDouble会把工资字段拆成double流然后流内部把每个元素逐个喂给DoubleSummaryStatistics。为什么要强调一遍过因为传统写法如果你想拿到五个指标要么在一个循环里同时维护五个临时变量要么把数据存下来来回扫好几遍。而summaryStatistics在遍历过程中同时更新所有状态时间和空间上都是单趟、O(1)额外内存不需要把工资列表复制一份出来。这在数据量大、字段多的场景下优势非常明显。1.2 对比手写循环少写五十行还更稳没有这个类之前我写过很多次类似的循环long count 0; double sum 0; double min Double.POSITIVE_INFINITY; double max Double.NEGATIVE_INFINITY; for (Employee e : employees) { double salary e.getSalary(); count; sum salary; if (salary min) { min salary; } if (salary max) { max salary; } } double avg count 0 ? 0.0 : sum / count;这段代码看着还行但一旦需求变成按部门分别统计只统计工资大于某个阈值的员工循环体就开始膨胀if嵌套越来越多很容易在某个分支里忘了更新某个变量。DoubleSummaryStatistics的优势在于统计逻辑被封装好了我只关心取出指标不关心怎么算的。而且它天然和Stream的filter、map、groupingBy等操作组合统计条件可以灵活叠加代码结构反而是更清晰的。2. 员工薪资统计核心代码五个标准指标一次算完2.1 准备员工对象和测试数据先定义一个简单的员工类字段就按最常见的来姓名、部门、工资。public class Employee { private String name; private String department; private double salary; public Employee(String name, String department, double salary) { this.name name; this.department department; this.salary salary; } public String getName() { return name; } public String getDepartment() { return department; } public double getSalary() { return salary; } Override public String toString() { return Employee{ name name \ , department department \ , salary salary }; } }测试数据我故意制造了一点戏剧性让最高工资出现两次方便后面验证最高工资人数的逻辑。ListEmployee employees Arrays.asList( new Employee(张三, 技术部, 15000), new Employee(李四, 技术部, 18000), new Employee(王五, 市场部, 12000), new Employee(赵六, 市场部, 18000), new Employee(钱七, 人事部, 9800), new Employee(孙八, 人事部, 10500) );2.2 用summaryStatistics()一次性算出五大指标核心代码非常简单DoubleSummaryStatistics stats employees.stream() .mapToDouble(Employee::getSalary) .summaryStatistics(); System.out.println(员工人数 stats.getCount()); System.out.println(工资总和 stats.getSum()); System.out.println(平均工资 stats.getAverage()); System.out.println(最高工资 stats.getMax()); System.out.println(最低工资 stats.getMin());输出结果员工人数6 工资总和83300.0 平均工资13883.333333333334 最高工资18000.0 最低工资9800.0五个getter对应关系一目了然getCount()拿人数getSum()拿总和getAverage()拿平均getMax()和getMin()拿最高和最低。这里注意getAverage()返回的是double是sum除以count直接除出来的没有做四舍五入所以打印出来一长串小数。如果想要一个带格式的展示可以配合String.format或者BigDecimal做舍入System.out.printf(平均工资%.2f%n, stats.getAverage());2.3 五个getter的边界行为空流、NaN、无穷大这部分属于踩过坑才能记住的知识点务必留意。当员工集合为空时DoubleSummaryStatistics默认状态是getCount()返回0getSum()返回0.0getAverage()返回0.0getMin()返回Double.POSITIVE_INFINITYgetMax()返回Double.NEGATIVE_INFINITY你没看错空集合下最低工资是正无穷、最高工资是负无穷。这个设计其实是有意为之的min和max在内部初始化成两个极端默认值方便第一次接收数据时直接替换。但如果你不管三七二十一直接展示这两个值页面上就会出现最高工资-Infinity这种让人摸不着头脑的东西。所以稳妥的做法是拿到stats之后先判断getCount() 0再决定要不要展示min和maxif (stats.getCount() 0) { System.out.println(暂无员工数据); } else { System.out.println(最低工资 stats.getMin()); System.out.println(最高工资 stats.getMax()); }另外如果数据里混入了NaNgetMax()会返回NaN因为内部比较时NaN不等于任何值也不小于任何值很容易把统计结果污染掉。我一般会在入参阶段就把非法值过滤掉filter(e - e.getSalary() 0)至少能把明显的脏数据挡在门外。3. 最高工资人数官方没给现成的三种思路自己补拿到五个标准指标之后领导要的第六个指标就来了拿最高工资的有几个人。说句实话DoubleSummaryStatistics没有提供这样的getter因为它内部只保存了max的值没有保存这个max出现了多少次。不过思路并不复杂本质就是统计最高工资在数据里的频次下面三种方案我都试过各有利弊。3.1 思路一先取max再二次过滤最简单第一种思路最直白先用summaryStatistics拿到最高工资然后重新遍历员工列表统计工资等于这个最高值的人数。DoubleSummaryStatistics stats employees.stream() .mapToDouble(Employee::getSalary) .summaryStatistics(); long maxSalaryCount employees.stream() .filter(e - Double.compare(e.getSalary(), stats.getMax()) 0) .count(); System.out.println(最高工资人数 maxSalaryCount);这里我用的是Double.compare(e.getSalary(), stats.getMax()) 0而不是直接写e.getSalary() stats.getMax()。原因很简单stats.getMax()拿到的最大值本身就是从这批数据里来的直接等值比较大多数情况下没问题但double的比较用总有踩NaN或-0.0这种特殊值的风险用Double.compare更稳妥这也是Java官方推荐的对象间double比较方式。这个方案的优点是肉眼可见的简单五行代码搞定缺点是遍历了两遍数据。在员工量级不大的场景下完全够用我实测过几万条数据二次过滤的耗时几乎可以忽略。3.2 思路二自定义Collector一趟遍历全搞定追求性能的话可以自己写一个容器类把最高工资人数这个状态一起维护进去。思路是在接收每个工资值时同步维护四个东西当前最大值、当前最大值出现次数、最小值、总和和计数。public class SalaryStats { private long count; private double sum; private double min Double.POSITIVE_INFINITY; private double max Double.NEGATIVE_INFINITY; private long maxCount 0; public void accept(Employee e) { double salary e.getSalary(); count; sum salary; if (salary min) { min salary; } if (salary max) { max salary; maxCount 1; } else if (salary max) { maxCount; } } public SalaryStats combine(SalaryStats other) { count other.count; sum other.sum; if (other.min min) { min other.min; } if (other.max max) { max other.max; maxCount other.maxCount; } else if (other.max max) { maxCount other.maxCount; } return this; } public double getAverage() { return count 0 ? 0.0 : sum / count; } // 省略getter }关键逻辑在accept里遇到新的更大值重置max并把maxCount设为1遇到等于当前max的值maxCount加一小于max的值只更新min和sum。这个模式保证maxCount永远跟随当前最大值的频次。使用方式是通过Collector.of接入StreamSalaryStats stats employees.stream().collect( Collector.of(SalaryStats::new, SalaryStats::accept, SalaryStats::combine) );为什么要写combine因为 parallelStream并行流会把数据分成多段分别统计最后把各段的中间结果合并combine就是干这个的。合并时需要注意如果两边的max相等maxCount要相加如果一边的max更大就取那边的maxCount。这个细节很容易写错我最初写合并逻辑时就漏了一方更大的情况。这个方案的优势是单趟遍历、常数级内存适合大列表或多次调用的场景劣势是代码量明显增加而且SalaryStats这个类需要自己维护如果没有对应测试覆盖出bug的概率不低。3.3 思路三groupingBy统计频率一表查到底第三种方案换了个角度干脆把工资按值分组数一下频次然后取频次表里最大工资对应的数量。MapDouble, Long salaryFreq employees.stream() .collect(Collectors.groupingBy(Employee::getSalary, Collectors.counting())); OptionalMap.EntryDouble, Long maxEntry salaryFreq.entrySet().stream() .max(Map.Entry.comparingByKey()); if (maxEntry.isPresent()) { double maxSalary maxEntry.get().getKey(); long maxSalaryCount maxEntry.get().getValue(); System.out.println(最高工资 maxSalary); System.out.println(最高工资人数 maxSalaryCount); }这里用groupingBy(Employee::getSalary, Collectors.counting())把每个工资值映射到出现次数然后通过entrySet().stream().max()找到key最大的那个Entry它的value就是我们要的人数。这个方案顺手把按工资分布也做出来了如果后续想看工资分布直方图这个Map直接就能用。唯一需要注意的是Double作为Map的key等值判断是按double的位模式走的正常场景没问题如果数据里同时存在0.0和-0.0这种极端值它们会被当成不同的key实际业务里基本遇不到但知道有这回事就好。3.4 三种方案怎么选成本、可读性、风险对比方案遍历次数代码量可读性主要风险先max再过滤2次最少最好大列表下多一次遍历自定义Collector1次最多一般combine逻辑容易写错groupingBy频次2次中等较好Double作为Map key的边界值我的建议是常规业务代码优先选方案一简洁直观别人接手一看就懂如果你在一个内层循环里反复调这个统计或者数据量到了百万级再考虑方案二方案三适合你恰好需要工资分布数据的场景顺手复用。没有银弹按需取舍。4. 封装成可复用的统计模块4.1 用独立类和Builder组织统计结果实际项目里不会只在一个地方用统计所以我会把结果封装成一个独立对象避免到处散落着DoubleSummaryStatistics的getter调用。比如定义SalaryReportpublic class SalaryReport { private final long count; private final double sum; private final double min; private final double max; private final double avg; private final long maxSalaryCount; // 全参构造器和getter省略 Override public String toString() { return String.format( 人数%d, 总和%.2f, 平均%.2f, 最低%.2f, 最高%.2f, 最高工资人数%d, count, sum, avg, min, max, maxSalaryCount); } }然后在统计入口处统一组装public SalaryReport buildReport(ListEmployee employees) { DoubleSummaryStatistics stats employees.stream() .mapToDouble(Employee::getSalary) .summaryStatistics(); double max stats.getMax(); long maxCount employees.stream() .filter(e - Double.compare(e.getSalary(), max) 0) .count(); return new SalaryReport(stats.getCount(), stats.getSum(), stats.getMin(), max, stats.getAverage(), maxCount); }这样调用方拿到的就是一个语义清晰的报表对象而不是一堆散装指标。UI层直接report.toString()就能打印出一行可读的汇总。4.2 Java 12的teeing一个collect返回完整报表Java 12引入了Collectors.teeing可以把两个收集器的结果合并成一个。这个特性非常适合我们的场景一个收集器算标准统计另一个收集器算最高工资频次最后合并成报表。SalaryReport report employees.stream().collect( Collectors.teeing( Collectors.summarizingDouble(Employee::getSalary), Collectors.groupingBy(Employee::getSalary, Collectors.counting()), (stats, freq) - { double max stats.getMax(); long maxCount freq.getOrDefault(max, 0L); return new SalaryReport(stats.getCount(), stats.getSum(), stats.getMin(), max, stats.getAverage(), maxCount); } ) );这段代码把统计和查频次并行执行再在合并函数里拼出SalaryReport。比起方案二的自定义Collectorteeing更语义化可读性也好。需要注意freq.getOrDefault(max, 0L)这一手万一数据为空stats.getMax()是负无穷大freq表里自然查不到兜底成0比抛异常体面得多。4.3 分部门统计groupingByDoubleSummaryStatistics组合把这个思路往外推一步就是按部门分别统计。用groupingBy配合Collectors.summarizingDouble即可MapString, DoubleSummaryStatistics deptStats employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.summarizingDouble(Employee::getSalary) )); deptStats.forEach((dept, stats) - System.out.printf(%s人数%d平均%.2f最高%.2f最低%.2f总和%.2f%n, dept, stats.getCount(), stats.getAverage(), stats.getMax(), stats.getMin(), stats.getSum()) );这个写法把分组和统计两件事组合在了一起Stream API对这类需求的表达能力是真的强。如果还想在部门维度上加最高工资人数那就要在groupingBy的value收集器上再动脑筋通常做法是先groupingBy到员工列表再对每个列表单独统计灵活度更高。5. 实操避坑这些问题我基本都踩过5.1 金额精度double算钱要谨慎DoubleSummaryStatistics底层是double而double在表示小数时本身就有精度问题。工资这种业务数据如果你只用来做报表展示double的误差在大多数情况下看不出来但只要涉及财务结算、对账或者金额要参与后续计算我强烈建议换成以分为单位的long用LongSummaryStatistics去统计或者干脆用BigDecimal手动聚合。BigDecimal total employees.stream() .map(e - BigDecimal.valueOf(e.getSalary())) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println(工资总和 total.setScale(2, RoundingMode.HALF_UP));这里BigDecimal.valueOf(double)会使用Double.toString的结果来构造能规避一部分二进制浮点误差是double转BigDecimal时的推荐做法。5.2 空集合与空值别让NPE毁掉统计两个最常见的线上事故源头。第一是员工列表为空前面说了min和max会变成正负无穷展示前一定先判断getCount() 0。第二是列表里有null元素或者某个员工的工资字段为null如果你用的是Double而不是doublemapToDouble会直接抛NPE。我在写统计逻辑前一般会在入口统一过滤一遍ListEmployee validEmployees employees.stream() .filter(Objects::nonNull) .filter(e - e.getSalary() 0) .collect(Collectors.toList());别小看这两行filter它能帮你挡掉大量边界脏数据让后面的统计逻辑保持简单。5.3 parallel流和自定义Collector的combine用parallelStream()配自定义Collector时combine的正确性至关重要。我吃过一次亏合并逻辑里只处理了两边max相等的情况没处理一边max更大的情况导致并行处理时最高工资人数统计错了好几倍。后来我加了一条铁律自定义Collector合并两个容器时先比较min和max再依据最大值来自哪一侧决定maxCount怎么合并顺序不能乱。如果不想自己维护这套逻辑建议在并行场景下直接用teeing或者回退到串行流。统计场景的数据量通常没大到非并行不可串行流的稳定性能已经够用。5.4 大数据量性能实测我在本地用十万条员工数据做过一次简单对比单次summaryStatistics()串行流耗时在几十毫秒内parallelStream()并不总是更快反而有线程调度开销小数据量下更慢。方案一的max后二次过滤在十万条数据下也基本感觉不到延迟所以性能不该成为你选复杂方案的核心理由代码可维护性才是。6. 后续还能怎么扩展6.1 用record/DTO封装统计结果Java 16可以改用record来承载统计结果代码会非常清爽public record SalaryReport( long count, double sum, double min, double max, double avg, long maxSalaryCount) { }record自动生成构造器、getter和toString用来做这种不可变的数据载体再合适不过。如果你的项目还在Java 11那老老实实写个普通类或者用Lombok的Value也行。6.2 结合BigDecimal做金额格式化报表最终要给人看double的原始输出太丑。我通常会在SalaryReport里加一个带格式的辅助方法public String toFormattedString() { DecimalFormat df new DecimalFormat(#,##0.00); return 人数 count , 总和 df.format(sum) , 平均 df.format(avg) , 最低 df.format(min) , 最高 df.format(max) , 最高工资人数 maxSalaryCount; }这样展示层只管调用不用关心金额格式化逻辑散落在哪里。我自己在实际项目里用得最多的组合是DoubleSummaryStatistics算五个标准指标再用一次filter把最高工资人数补上最后塞进一个DTO统一返回。简单、直观、团队成员接手也毫无压力。如果你想把工资统计做得更稳记住两个原则入参先清洗、空数据先判断。这套代码你完全可以照抄进自己的工具类下次再有类似的统计需求直接一行summaryStatistics()就能省下大半天的功夫。

相关新闻

SpringBoot+Vue儿童英语娱教系统开发全记录

SpringBoot+Vue儿童英语娱教系统开发全记录

最近一直在忙活一个毕设级的项目——基于SpringBoot和Vue的儿童英语娱教软件。说实话,做之前我觉得这类“少儿教育系统”无非就是CRUD加几个页面,真正上手才发现,儿童用户和成人用户的设计逻辑完全是两码事。这个项目收到的一些反馈也很有意思…

2026/10/3 4:04:56 阅读更多 →
动力电池SOH与RUL深度学习预测系统:LSTM+Attention实战部署

动力电池SOH与RUL深度学习预测系统:LSTM+Attention实战部署

简介:本资源是一套基于Python实现的动力电池健康状态(SOH)评估与剩余寿命(RUL)预测系统,面向计算机、人工智能、自动化及电子工程等专业的学生、教师与研发人员,适用于课程实践、毕业设计与学术…

2026/10/3 4:04:56 阅读更多 →
Redis密码设置与验证:从requirepass到ACL的完整安全链路

Redis密码设置与验证:从requirepass到ACL的完整安全链路

“Redis 密码设置和验证技巧”在面试题里通常被归成基础题,但我做了这么多年后端和中间件相关的技术面试,发现十个候选人里至少有四五个会在这道题上翻车。不是他们不知道requirepass,而是不知道密码配完以后会发生什么:连主从复制…

2026/10/3 4:04:56 阅读更多 →

最新新闻

OpenRig模拟赛车DIY座舱搭建指南:铝型材模块化从选料到调校

OpenRig模拟赛车DIY座舱搭建指南:铝型材模块化从选料到调校

和我这一代玩模拟赛车的人相处久了,都会经历一个绕不开的阶段:从“桌子支架凑合”切到“独立座舱”。OpenRig这个名称,最早是在模拟赛车的开源社区里见到的,它指的并不是某一家厂商的整机商品,而是一整套模块化驾驶舱的…

2026/10/3 4:45:24 阅读更多 →
SABRE 3D/3DxT安全配置必备前置清单:从环境评估到备份与回滚

SABRE 3D/3DxT安全配置必备前置清单:从环境评估到备份与回滚

每次接到SABRE 3D或者SABRE 3DxT的安全配置任务,我都习惯先沉住气,别急着打开组策略编辑器就开干。半导体设备不像普通办公电脑,你随手改一条安全策略,轻则报警满天飞,重则直接影响电镀腔体的工艺联锁,一批…

2026/10/3 4:44:19 阅读更多 →
Kubernetes配置更新不生效?用Reloader实现ConfigMap/Secret自动滚动重启

Kubernetes配置更新不生效?用Reloader实现ConfigMap/Secret自动滚动重启

先把话放这儿:我见过太多人改完 ConfigMap 之后,满怀信心敲下kubectl rollout restart deployment/xxx,结果业务日志里还是旧参数刷屏。这不是配置写错了,而是 Kubernetes 里“配置更新”和“应用生效”本身就是两码事。在 Kubern…

2026/10/3 4:44:11 阅读更多 →
原创世界观体系构建全流程:从宇宙论到悖释道诠

原创世界观体系构建全流程:从宇宙论到悖释道诠

1. 从零开始构建一部自创经典,我为什么先写“宇宙论”《凌微经悖释道诠》这六个字,大概是我折腾了最久的一个命名。总篇第三章单拎出来叫“宇宙论”,听起来像学术论文的分章,但实际这东西完全不是学术路线——它是我自己搭建的一套…

2026/10/3 4:44:07 阅读更多 →
一维伽辽金无网格法MATLAB实现:MLS形函数、参数调试与收敛性验证

一维伽辽金无网格法MATLAB实现:MLS形函数、参数调试与收敛性验证

简介:资源内容为一维伽辽金型无网格法的MATLAB编程实现,面向正在学习无网格法、伽辽金法或需要编写数值计算代码的高校学生与科研人员。程序主体是一个可运行的m脚本文件,配合一个rar压缩包,共两个文件,整体仅8KB&…

2026/10/3 4:44:00 阅读更多 →
基于Univer实现可自定义模板的锁定单元格填写表格

基于Univer实现可自定义模板的锁定单元格填写表格

先聊个真实场景。前阵子有个做内部运营系统的朋友找我,说他们的需求听起来很简单:给业务部门做一个“项目信息收集表”,表格由管理员定义好表头和结构,提交人只能在几个指定的白色单元格里填内容,其余区域雷打不动。他…

2026/10/3 4:43:49 阅读更多 →

日新闻

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南 【免费下载链接】ex-skill 前任 skill 项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill 前任.skill 是一个运行在 Claude Code 上的开源 Skill:导入微信、iMessage、短信、…

2026/10/3 0:00:27 阅读更多 →
45个经典Linux面试题:从命令到网络排障的完整考点解析

45个经典Linux面试题:从命令到网络排障的完整考点解析

刚开始带应届生的时候,我最头疼的就是他们拿着一摞Linux面试题背得滚瓜烂熟,一上机全露馅。后来自己从被面的人变成面别人的人,才慢慢摸清楚:Linux面试题考的根本不是答案本身,而是你面对一个不确定的系统问题时&#…

2026/10/3 0:01:28 阅读更多 →
SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

简介:本资源是一份面向SAP ABAP开发人员、生产计划专员及ERP实施顾问的实操型操作指南,聚焦SAP生产预留核心业务场景,系统解决物料预留创建、查询、校验与批量处理等高频问题。文档以结构化方式覆盖预留背景原理、OMC2编码规则、工厂级参数配…

2026/10/3 0:01:28 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 19:40:48 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/1 19:41:40 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/10/1 20:05:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/2 10:36:31 阅读更多 →
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/2 5:26:06 阅读更多 →
黑夜航拍船只数据集训练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/2 6:09:11 阅读更多 →