走进 Gang of Four 设计模式:享元模式
走进 Gang of Four 设计模式享元模式说明不仅告诉你怎么用更告诉你为什么这样设计前置知识Java 面向对象基础接口、HashMap、不可变对象、内存管理基本概念 设计模式分类总览GoF 23 种设计模式可按两个维度交叉分类类/对象处理方式 ×创建型/结构型/行为型目的。维度创建型Creational结构型Structural行为型Behavioral类Class通过继承复用Factory Method工厂方法Adapter类适配器Interpreter解释器Template Method模板方法对象Object通过组合/聚合复用Abstract Factory抽象工厂Builder建造者Prototype原型Singleton单例Adapter对象适配器Bridge桥接Composite组合Decorator装饰Facade外观Flyweight享元Proxy代理Chain of Resp.责任链Command命令Iterator迭代器Mediator中介者Memento备忘录Observer观察者State状态Strategy策略Visitor访问者 目录模式概述 · 15 问深度分析框架源码实战分析JDK Integer.valueOf 与 String 常量池Spring 单例 Bean 容器MyBatis SqlNode 共享元数据Tomcat StringCache深度追问Why / How / Trade-off / Evolution / Modern Practice总结参考文献1. 模式概述 · 15 问深度分析Q1: 为什么需要这个模式它解决了什么问题在一些应用中可能会出现需要创建海量相似对象的情况例如文本编辑器中的每一个字符、游戏中的成千上万个小兵、地图上的每一个建筑图标。解决的问题解决了由于大量对象的创建而导致的内存溢出OOM或内存开销过大、GC 过于频繁导致系统卡顿的问题。根本目的通过共享技术实现相同或相似对象的重用大幅减少内存中对象的数量。Q2: 如果不用这个模式会有什么缺陷内存暴涨如果每一个微小的实例都独立占用内存内存会迅速被吃光。GC 频繁生命周期短暂的大量对象会频繁触发 JVM 的 Young GC 甚至 Full GC导致系统出现明显的 STW 卡顿。创建开销大频繁地new对象并进行初始化消耗大量 CPU 资源和时间。Q3: 核心思想是什么一句话如何概括核心思想将对象的状态划分为内部状态Internal State和外部状态External State。内部状态可共享外部状态由客户端传入。一句话概括运用共享技术有效地支持大量细粒度的对象。Q4: 包含哪些角色每个角色的职责是什么角色职责Flyweight抽象享元定义产品的抽象接口包含需要外部状态的方法ConcreteFlyweight具体享元实现抽象接口保存内部状态不可变、可共享FlyweightFactory享元工厂核心角色管理享元对象池检查复用或创建Client客户端维护外部状态调用时传入享元方法Q5: 它们之间如何协作调用流程是怎样的客户端需要对象时不直接new而是向FlyweightFactory请求传入标识Key。FlyweightFactory检查缓存中是否存在该 Key如果有直接返回。如果没有创建一个新的ConcreteFlyweight存入缓存然后返回。客户端获取享元对象后在调用其方法时将外部状态作为参数传递进去。享元对象结合自身内部状态和传入的外部状态完成业务逻辑。Q6: 为什么要这样设计每个角色存在的意义是什么把外部状态剥离出来这是享元模式能成功缩减对象的关键。如果对象自己持有所有状态它就无法被别人复用。工厂Factory存在的意义它是共享的控制中心。没有工厂统一管理客户端各自new对象共享就无从谈起。具体享元存在的意义它是真正的业务承载者只保存不管在什么环境下都不变的硬核心数据内部状态。Q7: 为什么使用接口、抽象类、组合而不是其他方式为什么用接口/抽象类为了多态性。工厂返回统一的抽象享元类型客户端不需要知道具体拿到的是哪一个共享实例。为什么用组合通过参数传入外部状态外部状态是多变的如果使用继承每多一种外部状态就要派生一个子类导致类爆炸。通过组合/参数传递把外部状态变成行为的入参完美实现解耦。Q8: 符合哪些面向对象原则单一职责原则SRP享元对象只负责维护内部状态和核心逻辑工厂只负责对象的生命周期和缓存管理。开闭原则OCP增加新的具体享元类型只需扩展抽象享元并让工厂支持即可。面向接口编程DIP客户端依赖的是Flyweight接口而不是具体的实现类。Q9: 优缺点与额外成本优点极大减少内存中对象的数量降低 OOM 风险。外部状态独立不会相互影响。局限性/额外成本系统复杂度显著提高需要分离内部状态和外部状态增加了代码理解成本。运行时间成本每次读取/计算外部状态都需要通过参数传递属于用时间换空间。Q10: 最适合/不该用的场景最适合系统中有大量相同或相似的对象导致内存开销巨大。对象的大多数状态都可以变为外部状态。典型的如围棋/象棋游戏、文本渲染引擎、地图 POI 标记。不应该使用对象数量很少或对象之间几乎没有任何共同状态。内存绰绰有余而系统对执行速度有极高的要求。Q11: 与其他模式的区别与单例模式的区别单例模式是全局只有一个实例。享元模式在工厂里管理着一组/多个不同的对象实例。与原型模式的区别原型模式是通过克隆已有的对象来创建全新的独立对象不共享内存。享元模式是为了复用同一个对象减少创建。与组合模式的联系享元模式经常和组合模式结合使用。组合模式中的叶子节点如果数量巨大且类似可以设计为享元对象。Q12: 框架中的典型应用JDKInteger.valueOf(int i)内部有IntegerCache缓存 -128 到 127 之间的 Integer 对象。String常量池也是极典型的享元应用。SpringSpring 的 Bean 容器默认 Singleton 作用域管理着各种单例 Bean通过beanName在 Map 中复用对象。MyBatisSqlNode的共享元数据SQL 解析后的结构树被缓存复用。TomcatStringCache字符串缓存复用常用的 HTTP 头部、方法名等字符串。Q13: 最小可运行版本五子棋棋子importjava.util.HashMap;importjava.util.Map;// 1. 抽象享元interfaceChessPiece{voiddraw(intx,inty);// x, y 是外部状态}// 2. 具体享元只包含内部状态classConcreteChessPieceimplementsChessPiece{privatefinalStringcolor;// 内部状态颜色publicConcreteChessPiece(Stringcolor){this.colorcolor;}Overridepublicvoiddraw(intx,inty){System.out.println(在坐标 (x, y) 放置了一颗 [color] 棋子);}}// 3. 享元工厂classChessPieceFactory{privatestaticfinalMapString,ChessPiecepoolnewHashMap();publicstaticChessPiecegetChessPiece(Stringcolor){if(!pool.containsKey(color)){pool.put(color,newConcreteChessPiece(color));System.out.println( 创建了新的 [color] 棋子对象 );}returnpool.get(color);}}// 4. 客户端publicclassMain{publicstaticvoidmain(String[]args){ChessPieceblack1ChessPieceFactory.getChessPiece(黑色);black1.draw(1,1);ChessPiecewhite1ChessPieceFactory.getChessPiece(白色);white1.draw(2,2);ChessPieceblack2ChessPieceFactory.getChessPiece(黑色);black2.draw(3,3);ChessPiecewhite2ChessPieceFactory.getChessPiece(白色);white2.draw(4,4);System.out.println(black1 black2: (black1black2));// true}}Q14: 识别重构信号内存监控异常通过 Profiler 发现某种简单对象的实例数达到数十万甚至数百万占用了大部分堆内存。代码中存在大量重复数据发现这些对象中有 80% 的属性字段全都是一模一样的。Q15: 现代改进Java 16 Recordpublic record ChessPiece(String color) { public void draw(int x, int y) { ... } }函数式工厂ComputeIfAbsentpublicclassChessFactory{privatestaticfinalMapString,ChessPiecepoolnewConcurrentHashMap();publicstaticChessPieceget(Stringcolor){returnpool.computeIfAbsent(color,ConcreteChessPiece::new);}}微服务/分布式视角在微服务架构中单机的享元扩展成了分布式缓存/元数据服务。海量订单中重复的商品详情静态元数据通过 Redis 缓存或在 BFF 层进行数据聚合拉取核心思想依然是剥离外部动态状态共享静态内部状态。2. 框架源码实战分析2.1 JDK Integer.valueOf 与 String 常量池Integer.valueOf 源码publicstaticIntegervalueOf(inti){if(iIntegerCache.lowiIntegerCache.high)returnIntegerCache.cache[i(-IntegerCache.low)];returnnewInteger(i);}IntegerCache 享元池privatestaticclassIntegerCache{staticfinalintlow-128;staticfinalinthigh;staticfinalInteger[]cache;static{inth127;highh;cachenewInteger[(high-low)1];intjlow;for(intk0;kcache.length;k)cache[k]newInteger(j);}}内部状态共享Integer的value属性private final int value天然不可变线程安全。外部状态无属于单纯享元模式核心在于用空间换取高频数字的创建时间与内存开销。String 常量池Java 的String常量池StringTable在 JVM 层面是一个固定大小的 HashTable。使用字面量String s abc时JVM 先去StringTable检查是否存在值为abc的引用。存在则直接返回不存在则创建并存入。2.2 Spring 单例 Bean 容器Spring 的单例 Bean 容器从设计初衷上看其核心逻辑与享元工厂非常相似。核心源码——DefaultSingletonBeanRegistry/** Cache of singleton objects: bean name to bean instance. */privatefinalMapString,ObjectsingletonObjectsnewConcurrentHashMap(256);AbstractBeanFactory.doGetBean获取单例 BeanprotectedTTdoGetBean(Stringname,NullableClassTrequiredType,...){// 1. 尝试从单例享元池中直接获取ObjectsharedInstancegetSingleton(beanName);if(sharedInstance!nullargsnull){beangetObjectForBeanInstance(sharedInstance,name,beanName,null);}else{// 2. 享元池里没有创建并缓存if(isSingleton(beanName)){sharedInstancegetSingleton(beanName,()-createBean(beanName,mbd,args));beangetObjectForBeanInstance(sharedInstance,name,beanName,mbd);}}return(T)bean;}内部状态共享Bean 自身的类结构、无状态的方法、无状态的配置属性。外部状态非共享每一次具体业务请求传入的参数。为什么说是变形享元模式处理大量细粒度对象Spring Bean 通常是大粒度的。但从通过beanName去注册表中获取并复用无状态对象避免重复创建的角度来看两者的底层逻辑完全一致。2.3 MyBatis SqlNode 共享元数据MyBatis 的 XML 动态标签在解析后被转换成SqlNode树这些节点被一次性构建并缓存起来。当多个线程高并发执行同一条动态 SQL 时大家复用同一个IfSqlNode实例只需传入各自不同的DynamicContext。publicclassIfSqlNodeimplementsSqlNode{privatefinalExpressionEvaluatorevaluator;privatefinalStringtest;// 内部状态表达式文本privatefinalSqlNodecontents;// 内部状态子 SQL 节点Overridepublicbooleanapply(DynamicContextcontext){// context 是外部状态包含了本次调用的实际入参if(evaluator.evaluateBoolean(test,context.getBindings())){contents.apply(context);returntrue;}returnfalse;}}内部状态共享test表达式字符串、contents子节点。XML 加载时确定无论谁调用都不会变。外部状态通过参数传入DynamicContext context承载本次请求的实际入参和StringBuilder。2.4 Tomcat StringCache高并发 HTTP 请求带来大量重复字符串GET、POST、HTTP/1.1、Host。Tomcat 设计了StringCache来复用这些字符串。核心逻辑简化版publicclassStringCache{protectedstaticByteEntry[][]bcCachenull;protectedstaticCharEntry[][]ccCachenull;publicstaticStringtoString(ByteChunkbc){if(!cacheEnabled){returnbc.toStringInternal();// 没开启缓存直接 new String}inthashbc.getHash();intindexfindClosest(bc,bcCache[hashaccessCount]);if(index0){returnbcCache[hashaccessCount][index].name;// 命中缓存直接返回}else{Stringvaluebc.toStringInternal();// 高频访问才加入享元池returnvalue;}}}内部状态ByteEntry.name已经转换好的String对象。外部状态网络中源源不断流进来的原始 TCP 字节流ByteChunk。应用价值Tomcat 每秒处理数万次请求Connection: keep-alive、Accept-Language等常见 HTTP 头文本在内存中只保留极少数的单例其余全部复用引用对压榨 JVM 内存、减少 GC 停顿起到决定性作用。四者对比总结框架/组件享元工厂享元池内部状态外部状态JDK IntegerInteger类本身IntegerCache.cache数组-128~127的int值无单纯享元SpringBeanFactorysingletonObjectsMapBean 方法体、静态配置业务方法的具体入参MyBatisXMLScriptBuilderDynamicSqlSource树结构XML 静态文本和 OGNL 表达式DynamicContext参数绑定TomcatStringCache类bcCache/ccCache数组GET,Host等字符串原始 TCP 字节流ByteChunk3. 深度追问3.1 Why为什么解决哪个根本矛盾享元模式解决的是**业务诉求上无限的细粒度实体对象与物理层面上有限的计算机内存空间之间的根本矛盾。**面向对象设计鼓励将世界拆解得越细越好如文档编辑器里每个字符都应是对象但物理硬件的内存是有限的。享元模式允许我们在逻辑上维持成千上万个独立的细粒度对象但在物理上只占用极少量的内存。3.2 How怎么做通过哪些对象关系和交互机制享元模式的核心机制是状态分离与参数化组合A. 状态的彻底剥离内部状态存储在享元内部、不会随环境改变而改变的共享部分如棋子的颜色作为成员变量存在。外部状态随环境改变而改变、无法共享的部分如棋子的 X/Y 坐标坚决不能作为成员变量。B. 行为的参数化交互客户端在调用享元对象的方法时必须将外部状态通过方法参数传入。3.3 Trade-off代价牺牲了什么换取什么换取收益牺牲代价极大降低内存占用规避 OOM运行时间成本外部状态变成方法入参需动态计算/查找降低 GC 频率系统更平稳代码内聚性逻辑被撕裂在享元和客户端之间系统复杂度引入工厂、缓存、线程安全控制3.4 Evolution演化如何逐步演化而来普通工厂每次都返回新实例。普通缓存在工厂里加 Map 缓存发现相同 Key 就返回同一个。遇到瓶颈缓存的对象持有特定环境数据多线程并发时互相覆盖。终极形态Flyweight强制把变动的数据从对象中剥离只缓存纯净的核心。衍生变体单纯享元模式所有享元对象都可共享。复合享元模式多个单纯享元对象通过组合模式拼接而成复合享元本身不能共享但其内部子节点是共享的。3.5 Modern Practice现代实践是否被取代被现代技术部分取代/弱化分布式缓存Redis传统单机需要享元省内存现在大型微服务中分布式缓存承担了全局享元池的角色。依赖注入容器Spring IoC 容器本身就是对开发者透明的享元工厂不需要自己写FlyweightFactory。与现代编程范式完美共生函数式编程与不可变性享元模式的内部状态要求绝对不可变与函数式编程理念一拍即合。Java 16 的record让编写不可变享元类变得极为简单。无状态架构现代互联网高并发架构提倡服务无状态。API 网关、Controller/Service 的本质都是享元模式思想的无意识应用——对象只有一份单例高并发的 HTTP 请求外部状态作为参数流经这些无状态的服务节点。4. 总结核心要点享元模式通过共享对象减少内存消耗核心机制是状态分离内部状态共享由享元存储外部状态可变由客户端传入与单例模式的区别享元是一组对象单例是一个对象四个工业级应用IntegerCache数字缓存、SpringsingletonObjectsBean 池、MyBatisSqlNodeSQL 语义共享、TomcatStringCacheHTTP 头部复用现代演进record不可变性、ConcurrentHashMap.computeIfAbsent、分布式 Redis 缓存一句话享元模式是分门别类同类共享——把对象的相同部分抽出来共享把不同部分交给客户端自己管理。5. 参考文献[1] GAMMA E, HELM R, JOHNSON R, et al. Design Patterns: Elements of Reusable Object-Oriented Software[M]. Boston: Addison-Wesley, 1994.[2] Oracle. Java Integer / String Source Code[EB/OL]. https://docs.oracle.com/javase/8/docs/api/, 2024.[3] SPRING. Spring BeanFactory[EB/OL]. https://docs.spring.io/spring-framework/, 2024.[4] MyBatis. MyBatis 3 SqlNode[EB/OL]. https://github.com/mybatis/mybatis-3, 2024.[5] Apache Tomcat. Tomcat StringCache[EB/OL]. https://tomcat.apache.org/, 2024.

相关新闻

C++后台服务集成MQTT:基于Paho与RabbitMQ的实战指南

C++后台服务集成MQTT:基于Paho与RabbitMQ的实战指南

1. 项目概述:为什么选择Paho C与RabbitMQ?如果你正在用C开发一个需要跨设备、跨网络通信的后台服务,比如物联网设备管理平台、分布式系统的监控节点,或者一个高性能的实时数据分发中间件,那么“消息队列”这个概念你肯…

2026/10/15 16:08:34 阅读更多 →
走进 Gang of Four 设计模式:命令模式

走进 Gang of Four 设计模式:命令模式

走进 Gang of Four 设计模式:命令模式 说明:不仅告诉你"怎么用",更告诉你"为什么这样设计" 前置知识:Java 面向对象基础(接口、多态)、回调思想、Runnable 基础 📊 设计模式…

2026/10/15 16:49:08 阅读更多 →
C#大漠插件多线程架构实战:基于Thread类实现多窗口自动化

C#大漠插件多线程架构实战:基于Thread类实现多窗口自动化

1. 项目概述与核心价值如果你正在用C#和大漠插件做游戏自动化,尤其是需要同时操作多个游戏窗口,那么“多线程”就是你绕不开的一道坎。单线程脚本跑一个窗口还行,一旦窗口数量上来,要么卡成幻灯片,要么逻辑错乱。我见过…

2026/10/15 16:48:58 阅读更多 →

最新新闻

VSCode配置Anaconda全指南:解决Python解释器与conda环境问题

VSCode配置Anaconda全指南:解决Python解释器与conda环境问题

配过的人都有体会——Anaconda装了,VSCode也装了,结果新建一个Python文件,import numpy直接给你飘红,运行的时候又说找不到模块。这时候大多数人第一个想法是“Anaconda没装好”“VSCode坏了”,但真正的原因往往是VSCo…

2026/10/16 17:55:24 阅读更多 →
分布式存储选型与落地:Ceph、HDFS、GlusterFS、MinIO对比与避坑指南

分布式存储选型与落地:Ceph、HDFS、GlusterFS、MinIO对比与避坑指南

简介:一份关于主流分布式存储技术的综述文档,适合大数据处理、云计算及后端存储方向的开发者与架构师阅读。内容以架构与演化两条主线展开,系统梳理了谷歌文件系统、Hadoop分布式文件系统、Minio对象存储等经典方案的设计思想与适用场景。压缩…

2026/10/16 17:55:24 阅读更多 →
Matlab图像拼接实战:SIFT特征匹配+RANSAC单应估计+多频带融合

Matlab图像拼接实战:SIFT特征匹配+RANSAC单应估计+多频带融合

简介:本资源是一套基于Matlab实现的SIFT特征匹配与RANSAC鲁棒估计相结合的图像拼接与融合完整方案,面向计算机视觉初学者、图像处理课程学习者及科研入门人员,解决多视角图像自动对齐、宽视场全景图生成等典型问题。压缩包共20个文件&#xf…

2026/10/16 17:55:24 阅读更多 →
用Python爬虫打造自动化SEO巡检工具:从TDK提取到反爬避坑

用Python爬虫打造自动化SEO巡检工具:从TDK提取到反爬避坑

简介:面向网站优化人员与Python学习者的SEO数据采集与分析工具,以“SEO网络蜘蛛侠”命名,通过自动化爬虫帮助用户完成关键词挖掘、元信息检查、链接结构分析、内容质量与加载速度检测、死链排查等高频SEO任务。程序内置可执行版本&#xff0c…

2026/10/16 17:55:24 阅读更多 →
Python快速可视化SEG-Y地震数据:三步实现剖面绘制与批量抽检

Python快速可视化SEG-Y地震数据:三步实现剖面绘制与批量抽检

简介:这份资源面向地质勘探、地震预测与环境监测领域的技术人员及Qt开发者,提供一套基于Qt3.3框架的地震数据快速可视化实现方案,重点解决大规模地震记录的读取、处理与图形化展示问题。压缩包共77个文件,约2.07MB,以h…

2026/10/16 17:55:23 阅读更多 →
Android应用目录结构全解析:从Gradle配置到资源文件管理

Android应用目录结构全解析:从Gradle配置到资源文件管理

很多刚接触Android开发的朋友,第一次用Android Studio新建项目时,看到左侧那一大坨目录树往往会有点懵。我当年也是从一个个文件夹点开、再关掉,折腾了好几天才搞清楚哪个目录是存代码的、哪个目录是放图片的、哪个文件是注册Activity的。这篇…

2026/10/16 17:54:23 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

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

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04: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/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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →