首页/新闻资讯/正文详情

PageHelper分页原理、实战优化与避坑指南

发布时间:2026/9/27 1:51:07 来源:云帆数科 栏目:资讯中心
PageHelper分页原理、实战优化与避坑指南
1. 为什么分页这件事值得单独拎出来讲做业务系统的人对分页都不陌生。一个订单列表、一个用户管理页、一个日志查询界面只要数据量稍微上去一点全量查询就是灾难。我见过不少项目在初期数据量小的时候直接selectList一把梭等到表里几十万行数据的时候接口响应从几十毫秒飙到几秒前端转圈转到用户想砸键盘。分页的本质是把取全部变成取一页。听起来简单但真要做好涉及的东西不少SQL 怎么改写、总数怎么算、参数怎么传、不同数据库方言怎么兼容、和 MyBatis 的拦截器机制怎么配合。PageHelper 这个组件之所以流行就是因为它把这些脏活累活都封装了你只需要在查询前写一行PageHelper.startPage(pageNum, pageSize)后面的分页逻辑它自动帮你处理。但用起来简单和用对了是两回事。我见过太多人用了 PageHelper 之后踩坑分页失效、总数不对、线程安全问题、和自定义拦截器冲突、count 查询慢到爆炸。这些问题在官方文档里往往一笔带过但在真实项目里一个比一个致命。这篇内容我打算把 PageHelper 从里到外讲透。不是那种复制粘贴就能跑的教程而是把它的工作原理、源码层面的拦截机制、各种边界情况的处理、以及我在实际项目里踩过的坑都摊开来讲。适合已经用过 MyBatis、想真正搞懂分页组件的人也适合正在选型、纠结要不要引入 PageHelper 的人。2. PageHelper 到底在 MyBatis 的哪个环节动了手脚2.1 从一次普通查询到分页查询的转变过程先看一段最普通的 MyBatis 查询代码ListOrder orders orderMapper.selectByUserId(userId);这条语句最终会走 MyBatis 的Executor.query方法经过 SQL 解析、参数映射、执行、结果集映射几个阶段。PageHelper 要做的就是在 SQL 真正发给数据库之前把它改写成带分页的语句。它的实现方式是 MyBatis 的Interceptor拦截器机制。MyBatis 允许你在四大核心对象Executor、StatementHandler、ParameterHandler、ResultSetHandler的方法执行前后插入自定义逻辑。PageHelper 选择拦截Executor的query方法因为这是查询执行的入口。具体来说PageHelper 实现了org.apache.ibatis.plugin.Interceptor接口通过Intercepts注解声明它要拦截的目标Intercepts({ Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })这里拦截了两个重载的query方法是因为 MyBatis 在不同版本和不同调用路径下会走不同的重载。两个都拦上才能保证不漏。2.2 拦截之后PageHelper 做了哪几件事当query方法被拦截后PageHelper 的核心逻辑在PageInterceptor.intercept方法里。整个流程可以拆成这么几步第一步检查是否需要分页。它会从当前线程的ThreadLocal里取出之前startPage存进去的分页参数。如果取不到说明这次查询不需要分页直接放行原方法。第二步判断是否需要 count 查询。如果分页参数里没有指定countfalse它会先根据原始 SQL 生成一条 count 语句执行它拿到总记录数。第三步改写 SQL。根据配置的方言MySQL、Oracle、PostgreSQL 等在原始 SQL 后面拼接limit、rownum或者offset fetch之类的分页语法。第四步执行分页查询。用改写后的 SQL 去查当前页的数据。第五步包装结果。把查询结果封装成Page对象这个对象继承自ArrayList同时携带了总记录数、总页数、当前页码等分页信息。第六步清理 ThreadLocal。在finally块里调用clearPage()防止分页参数残留影响下一次查询。这六步里最容易出问题的就是第一步和第六步。ThreadLocal 用得好是便利用不好就是灾难。2.3 ThreadLocal 带来的便利与隐患PageHelper.startPage()的本质是把分页参数塞进当前线程的ThreadLocal变量里。这样设计的好处是你不需要修改 Mapper 接口的方法签名不需要额外传分页参数代码侵入性极低。但代价是什么分页参数和线程绑定了。这意味着如果你在startPage之后、真正执行查询之前中间又调用了别的查询那个查询也会被分页。这是最常见的坑。如果你用了线程池线程被复用上一次请求的分页参数如果没清理干净下一次请求可能莫名其妙被分页。如果你在异步线程里执行查询startPage在主线程调用查询在子线程执行ThreadLocal 取不到值分页直接失效。我见过一个真实案例某项目在startPage之后先查了一次用户权限再查订单列表。结果用户权限查询也被加上了limit导致权限判断出错。这种 bug 排查起来非常费劲因为代码逻辑看起来完全正常。正确的做法是startPage之后紧跟要分页的那条查询中间不要插入任何其他数据库操作。如果确实需要先做别的查询把分页查询放到最后或者用PageHelper.startPage的重载方法明确指定。3. 把 PageHelper 集成进 Spring Boot 项目的完整路径3.1 依赖引入与版本选择的门道在 Spring Boot 项目里引入 PageHelper需要加两个依赖dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency注意这里用的是pagehelper-spring-boot-starter不是单纯的pagehelper。前者帮你做了自动配置后者需要你手动注册拦截器。用 starter 省事但也要注意版本兼容问题。版本选择上有个经验PageHelper 的版本要和 MyBatis 的版本匹配。比如 MyBatis 3.5.x 一般配 PageHelper 5.3.x 或 1.4.x 的 starter。如果版本差太多可能出现拦截器签名不匹配、方法找不到之类的错误。我遇到过用 PageHelper 4.x 配 MyBatis 3.5.6 导致BoundSql相关方法报错的案例升级到 5.3.2 就好了。另外如果你项目里用的是 MyBatis-Plus它自带分页插件就不要同时引入 PageHelper 了两个拦截器会打架。MyBatis-Plus 的分页用法和 PageHelper 完全不同这个后面会提。3.2 配置文件里那几个关键参数在application.yml里PageHelper 的配置项不多但每一个都有讲究pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: countcountSql page-size-zero: false逐个说helper-dialect指定数据库方言。虽然 PageHelper 能自动检测但自动检测是靠 JDBC URL 判断的多数据源场景下可能判断错。显式指定最稳妥。支持的值有mysql、oracle、postgresql、sqlserver、db2、sqlite等。reasonable这个参数很实用。设为true时如果pageNum小于 1查第一页如果pageNum大于总页数查最后一页。设为false时页码越界就返回空结果。我一般建议设为true因为前端传参不可控用户手动改 URL 参数的情况太常见了。support-methods-arguments设为true后PageHelper 支持从 Mapper 方法的参数里自动识别分页参数。比如你的方法签名是selectByCondition(String name, int pageNum, int pageSize)它会自动把pageNum和pageSize当作分页参数。这个功能方便但容易误伤如果方法里恰好有叫pageNum的业务参数就麻烦了。我一般关掉它老老实实用startPage。params用于配置 count 查询的参数映射。默认是countcountSql意思是如果参数里有count这个键就用它的值作为 count 查询的 SQL。一般不用改。page-size-zero设为true时如果pageSize为 0会查询全部数据。默认是falsepageSize为 0 时返回空。这个参数看业务需求我倾向于保持false避免意外全量查询。3.3 一个能直接跑的最小示例光说配置不够直观上一个完整的例子。假设有一个用户表我们要分页查询实体类public class User { private Long id; private String name; private String email; // getter/setter 省略 }Mapper 接口Mapper public interface UserMapper { ListUser selectAll(); }Mapper XMLselect idselectAll resultTypecom.example.entity.User select id, name, email from user /selectService 层Service public class UserService { Autowired private UserMapper userMapper; public PageInfoUser getUserByPage(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.selectAll(); return new PageInfo(users); } }Controller 层RestController RequestMapping(/user) public class UserController { Autowired private UserService userService; GetMapping(/list) public PageInfoUser list(RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 10) int pageSize) { return userService.getUserByPage(pageNum, pageSize); } }这段代码跑起来访问/user/list?pageNum1pageSize10返回的 JSON 里会包含list当前页数据、total总记录数、pageNum、pageSize、pages总页数等字段。前端拿到这些信息就能渲染分页组件了。注意PageInfo这个类它是 PageHelper 提供的分页信息封装比直接用Page对象更友好字段命名更符合前端习惯。Page是ArrayList的子类PageInfo是独立的包装类我一般用PageInfo。4. count 查询慢的根因与几种实战优化手段4.1 为什么数据一多 count 就扛不住PageHelper 默认会执行一条 count 查询来算总数。这条 count 语句是它根据原始 SQL 自动生成的大致逻辑是把select 字段列表替换成select count(0)然后去掉order by之类的子句。问题在于自动生成的 count 语句往往不是最优的。举几个典型场景场景一多表 join。原始 SQL 是select a.*, b.name from a left join b on a.b_id b.id where a.status 1。PageHelper 生成的 count 是select count(0) from a left join b on a.b_id b.id where a.status 1。如果 join 的表很大这个 count 会非常慢。但实际上如果 join 不影响行数一对多关系里的一对一count 只需要查主表就行。场景二带 distinct 的查询。原始 SQL 有distinctcount 也得跟着 distinct否则总数会偏大。但 distinct 本身就很慢。场景三复杂的子查询和聚合。原始 SQL 里有group by、havingcount 语句生成后可能语义都不对了。场景四大表无索引。这个不用多说count 走全表扫描几百万行数据能跑好几秒。我在一个项目里遇到过订单表 800 万行带用户信息和商品信息的 join 查询PageHelper 自动生成的 count 跑了 4 秒多。后来手动优化成只 count 订单表降到 200 毫秒。4.2 手动指定 count 语句的两种方式PageHelper 提供了手动指定 count 语句的能力这是解决 count 慢最直接的手段。方式一在 Mapper XML 里定义 count 查询通过startPage的重载传入。PageHelper.startPage(pageNum, pageSize, true); // 或者 PageHelper.startPage(pageNum, pageSize, countSql);不过更常用的方式是在 XML 里写一个专门的 count 查询然后用PageHelper.startPage(pageNum, pageSize, false)关闭自动 count自己查总数。方式二利用params配置和 Mapper 方法参数。在 Mapper 接口里定义ListOrder selectOrderPage(Param(count) String countSql, Param(condition) OrderQuery query);然后在 XML 里用${count}动态拼接。这种方式灵活但要注意 SQL 注入风险${}是直接拼接不能用于用户输入。我更推荐第一种方式自己写 count 查询自己控制。比如select idcountOrder resultTypelong select count(0) from orders where if teststatus ! nulland status #{status}/if if testuserId ! nulland user_id #{userId}/if /where /selectService 里long total orderMapper.countOrder(query); PageHelper.startPage(pageNum, pageSize, false); ListOrder list orderMapper.selectOrderPage(query); PageInfoOrder pageInfo new PageInfo(list); pageInfo.setTotal(total);这样 count 语句完全由你控制可以针对性地加索引、去掉不必要的 join。4.3 用近似总数和不查总数换性能有些业务场景其实不需要精确的总数。比如日志查询、商品列表用户只关心有没有下一页不关心具体多少条。这时候可以方案一不查总数。PageHelper.startPage(pageNum, pageSize, false)然后PageInfo的total会是 0 或者当前页大小。前端用上一页/下一页而不是页码跳转。方案二查近似总数。用explain或者show table status拿一个估算值。MySQL 的explain select ...会返回rows字段是个估算值速度极快。适合对精度要求不高的场景。方案三缓存总数。如果数据变化不频繁可以把总数缓存起来比如放 Redis设置一个较短的过期时间。用户翻页时直接用缓存的总数只有缓存失效才重新 count。这三种方案我在不同项目里都用过。日志系统用方案一商品列表用方案三后台管理的数据统计用方案二。核心思路是不要为了一个用户根本不看的数字付出几秒的查询代价。4.4 count 查询的索引优化要点如果必须精确 count那索引就是关键。几个经验count 查询的 where 条件字段要有索引。复合索引要注意最左前缀原则。如果 count 语句里有 join确保 join 字段有索引。对于count(*)和count(0)MySQL 优化器会选最小的索引来扫描所以表上至少得有一个索引。如果 where 条件区分度很低比如 status 只有几个值索引效果有限可以考虑覆盖索引让 count 只扫索引不回表。我做过一个测试一张 500 万行的表count(*)无索引全表扫描 3.2 秒加了覆盖索引后降到 0.4 秒。差距非常明显。5. 那些官方文档不会告诉你的踩坑实录5.1 startPage 之后插入其他查询导致分页错乱这是 PageHelper 最经典的坑没有之一。看这段代码public PageInfoOrder getOrders(int pageNum, int pageSize, Long userId) { PageHelper.startPage(pageNum, pageSize); User user userMapper.selectById(userId); // 这条也被分页了 ListOrder orders orderMapper.selectByUserId(userId); return new PageInfo(orders); }userMapper.selectById这条查询会被 PageHelper 拦截加上limit 0, 10。如果用户表里 id 对应的记录不在前 10 条查出来就是 null。然后后面的订单查询反而正常分页了但PageInfo包装的是订单列表看起来一切正常实际上用户信息丢了。排查这种问题的思路如果发现某个查询莫名其妙返回空或者数据不对先看它前面有没有startPage。用 IDE 的调用链分析或者直接在startPage后面打日志。修复方式把startPage移到紧挨着分页查询的位置或者用PageHelper.clearPage()手动清理。5.2 线程池复用导致的分页参数串台这个坑更隐蔽。假设你用Async或者自定义线程池执行查询Async public CompletableFutureListOrder queryAsync(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListOrder orders orderMapper.selectAll(); return CompletableFuture.completedFuture(orders); }如果startPage和查询在同一个线程里没问题。但如果startPage在主线程调用查询被提交到线程池执行ThreadLocal 就取不到值了。还有一种情况线程池里的线程执行完查询后如果因为异常导致clearPage没执行分页参数会残留在 ThreadLocal 里。下一个任务复用这个线程时会莫名其妙被分页。防御手段在finally块里显式调用PageHelper.clearPage()。虽然 PageHelper 内部有清理逻辑但自己再清一次更保险。try { PageHelper.startPage(pageNum, pageSize); return orderMapper.selectAll(); } finally { PageHelper.clearPage(); }5.3 和自定义拦截器、MyBatis-Plus 的冲突PageHelper 本身是个拦截器如果你项目里还有其他 MyBatis 拦截器比如做数据权限的、做 SQL 日志的就可能冲突。冲突的表现形式多样有的拦截器顺序不对导致分页 SQL 没被改写有的拦截器拿到的是改写前的 SQL 导致日志不对还有的拦截器直接抛异常。解决思路通过Order注解或者配置文件的plugins顺序来控制拦截器执行顺序。PageHelper 的拦截器应该尽量靠前保证它在其他拦截器之前改写 SQL。如果同时用了 MyBatis-Plus那更麻烦。MyBatis-Plus 有自己的分页插件PaginationInnerInterceptor和 PageHelper 功能重叠。两个一起用轻则分页失效重则启动报错。二选一不要贪心。5.4 分页参数被前端篡改的安全问题pageSize如果完全由前端控制用户可以传pageSize100000一次性拉走所有数据。这既是性能问题也是安全问题。防御方式在 Service 层对pageSize做上限校验。private static final int MAX_PAGE_SIZE 100; public PageInfoOrder getOrders(int pageNum, int pageSize) { if (pageSize MAX_PAGE_SIZE) { pageSize MAX_PAGE_SIZE; } if (pageNum 1) { pageNum 1; } PageHelper.startPage(pageNum, pageSize); // ... }这个校验看起来简单但很多项目都忘了做。我审计过一个系统pageSize没限制有人写了个脚本传pageSize999999直接把数据库连接池打满了。6. PageHelper 和 MyBatis-Plus 分页的选型对比6.1 两者的设计哲学差异PageHelper 是无侵入路线你写正常的查询它在拦截器里帮你改 SQL。MyBatis-Plus 是增强路线它提供了BaseMapper、IPage、QueryWrapper等一套完整的 API分页是这套 API 的一部分。用 PageHelper你的 Mapper 还是标准的 MyBatis MapperXML 还是标准的 XML。用 MyBatis-Plus你大概率会用它的selectPage方法配合QueryWrapper构造条件。从代码风格上说PageHelper 更贴近原生 MyBatis迁移成本低。MyBatis-Plus 功能更全但绑定更深。6.2 分页场景下的能力对照对比维度PageHelperMyBatis-Plus分页写法startPage 普通查询selectPageIPagecount 控制支持手动指定 count SQL支持optimizeCountSql配置多数据源需显式指定方言自动适配与 XML 配合完全兼容兼容但推荐用 Wrapper学习成本低中额外功能分页为主代码生成、逻辑删除、乐观锁等如果项目已经是 MyBatis-Plus 技术栈直接用它的分页插件没必要再引入 PageHelper。如果项目是原生 MyBatis只想解决分页问题PageHelper 更轻量。6.3 迁移和共存的注意事项从 PageHelper 迁移到 MyBatis-Plus 分页主要改三处Mapper 接口继承BaseMapper、Service 层改用selectPage、分页参数从startPage改成Page对象。如果非要共存必须确保两个分页插件不会同时生效。可以通过配置让 PageHelper 只对特定的 Mapper 生效或者干脆在 MyBatis-Plus 项目里禁用 PageHelper 的自动配置。我的建议是新项目如果打算用 MyBatis-Plus就别引入 PageHelper。老项目用 PageHelper 用得好好的也别折腾迁移。技术选型最怕的就是为了新而新。7. 几个容易被忽略的进阶用法7.1 用 Page 对象直接接收分页结果除了PageInfoPageHelper 还提供了Page类。Page继承自ArrayList所以你可以直接用PageT接收查询结果PageHelper.startPage(pageNum, pageSize); PageOrder page (PageOrder) orderMapper.selectAll(); long total page.getTotal(); int pages page.getPages();这种写法比PageInfo更直接但要注意强制类型转换。Page对象里的total、pages等字段是 PageHelper 在查询后填充的。7.2 分页查询返回自定义 VO 的处理如果 Mapper 返回的不是实体类而是自定义的 VO分页一样能用PageHelper.startPage(pageNum, pageSize); ListOrderVO list orderMapper.selectOrderVO(); PageInfoOrderVO pageInfo new PageInfo(list);PageInfo是泛型的支持任意类型。唯一要注意的是count 查询的 SQL 要能正确统计 VO 对应的记录数。如果 VO 是多表 join 的结果count 语句可能需要调整。7.3 在分页结果上做二次加工有时候分页查出来的数据还需要加工比如给每个订单补充用户信息。这时候要注意不要在分页查询和PageInfo包装之间插入其他查询否则会破坏分页信息。正确的做法是先拿到PageInfo再对pageInfo.getList()里的元素做加工。PageHelper.startPage(pageNum, pageSize); ListOrder orders orderMapper.selectAll(); PageInfoOrder pageInfo new PageInfo(orders); // 二次加工 ListLong userIds pageInfo.getList().stream() .map(Order::getUserId).collect(Collectors.toList()); MapLong, User userMap userMapper.selectByIds(userIds) .stream().collect(Collectors.toMap(User::getId, u - u)); pageInfo.getList().forEach(o - o.setUser(userMap.get(o.getUserId()))); return pageInfo;这样分页信息不会丢加工逻辑也清晰。7.4 分页参数的合理化处理前面提过reasonable配置这里再补充一个细节当pageNum超过总页数时如果reasonabletruePageHelper 会查最后一页。但如果总记录数是 0它会怎么处理实测下来总记录数为 0 时pageNum会被设为 1返回空列表。这个行为是合理的前端拿到空列表和total0可以正常显示暂无数据。另外pageSize为 0 时如果page-size-zerofalse返回空列表。如果设为true会查全部数据。这个配置要谨慎我一般保持默认。8. 从源码角度看 PageHelper 的几个关键实现8.1 Page 对象的继承结构PageE继承自ArrayListE同时实现了Serializable。它内部有几个关键字段pageNum当前页码pageSize每页大小total总记录数pages总页数reasonable是否启用合理化count是否执行 count 查询因为继承自ArrayList所以Page对象可以直接当List用。这也是为什么PageHelper.startPage之后查询返回的List实际上是个Page对象可以强转。8.2 count 查询的 SQL 生成逻辑PageHelper 生成 count SQL 的核心类叫CountSqlParser。它的处理逻辑大致是用 JSqlParser 解析原始 SQL得到Select对象。判断 SQL 是否可以被优化。比如有没有distinct、group by、having。如果可优化把select的字段替换成count(0)去掉order by。如果不可优化用select count(0) from (原始SQL) tmp的方式包一层。这个逻辑在大多数场景下没问题但遇到复杂 SQL 就可能生成低效甚至错误的 count 语句。这也是为什么我建议对性能敏感的场景手动指定 count SQL。8.3 方言适配的实现方式PageHelper 支持多种数据库方言每种方言对应一个AbstractHelperDialect的实现类。比如MySqlDialect会在 SQL 后面拼limit ?, ?OracleDialect会用rownum嵌套查询。方言的选择逻辑是如果配置了helper-dialect就用配置的否则从数据库连接里自动检测。自动检测靠的是 JDBC URL 里的数据库类型标识。多数据源场景下如果不同数据源是不同类型的数据库自动检测可能出错。这时候要么显式配置要么用PageHelper.startPage的重载方法指定方言。9. 实际项目中的分页方案决策清单9.1 什么时候该用 PageHelper项目是原生 MyBatis 或 MyBatis 为主没有用 MyBatis-Plus。分页需求集中在列表查询逻辑相对简单。团队对 MyBatis 拦截器机制有基本了解能排查常见问题。数据量在百万级以内count 查询性能可接受或者愿意做 count 优化。9.2 什么时候该考虑其他方案数据量特别大千万级以上count 查询成为瓶颈考虑不查总数或近似总数。项目已经用了 MyBatis-Plus直接用它的分页插件。分页逻辑非常复杂涉及多表、聚合、动态条件PageHelper 的自动 count 可能不准确需要更精细的控制。对性能要求极高愿意手写分页 SQL 和 count SQL完全掌控执行计划。9.3 一个可复用的分页工具类最后分享一个我在项目里常用的分页工具方法封装了参数校验和异常处理public class PageUtils { private static final int DEFAULT_PAGE_NUM 1; private static final int DEFAULT_PAGE_SIZE 10; private static final int MAX_PAGE_SIZE 100; public static T PageInfoT doPage(int pageNum, int pageSize, SupplierListT query) { int safePageNum Math.max(pageNum, DEFAULT_PAGE_NUM); int safePageSize pageSize 0 ? DEFAULT_PAGE_SIZE : Math.min(pageSize, MAX_PAGE_SIZE); try { PageHelper.startPage(safePageNum, safePageSize); ListT list query.get(); return new PageInfo(list); } finally { PageHelper.clearPage(); } } }用的时候PageInfoOrder pageInfo PageUtils.doPage(pageNum, pageSize, () - orderMapper.selectByCondition(query));这个封装的好处是参数校验统一、ThreadLocal 清理有保障、调用方代码简洁。Supplier的写法保证了startPage和查询之间不会插入其他逻辑。我在实际使用中发现分页组件这种东西用起来越简单背后越需要理解它的机制。PageHelper 把复杂度藏起来了但藏起来的东西迟早会在某个边界场景里冒出来。把 ThreadLocal 的清理、count 的生成逻辑、拦截器的执行顺序这几件事搞清楚大部分坑都能提前避开。至于 count 慢的问题本质上不是 PageHelper 的锅而是 SQL 和索引的问题分页组件只是把这个问题放大了而已。

相关推荐

QT5离线下载全指南:绕过在线安装器,从镜像源到aqtinstall快速部署
QT5离线下载全指南:绕过在线安装器,从镜像源到aqtinstall快速部署

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

旧手机改造成家庭NAS:ARM设备搭建私有云全攻略
旧手机改造成家庭NAS:ARM设备搭建私有云全攻略

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

IEC61850模型建模与MMS报文实战解析
IEC61850模型建模与MMS报文实战解析

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

六因子选股与双指标择时:量化策略回测实战拆解
六因子选股与双指标择时:量化策略回测实战拆解

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

福田网站设计公司实战:3个步骤搞定性能优化
福田网站设计公司实战:3个步骤搞定性能优化

福田网站设计公司实战:3个步骤搞定性能优化 改个按钮颜色,建站公司让你等一周?这种体验太常见了。很多福田的企业老板都遇到过,明明只是微调需求,反馈却慢得像蜗牛。更让人头疼的是,网站上线后打开速度慢,客户等不及就走了。这时候你才意识到,找福田… · 2026/9/27 2:33:08

网站建设的需求分析报告速查手册:搞定域名服务器不踩坑
网站建设的需求分析报告速查手册:搞定域名服务器不踩坑

网站建设的需求分析报告速查手册:搞定域名服务器不踩坑 域名服务器搞不懂,是90%甲方在建站初期最大的拦路虎。很多浙江的老板找我们做网站,第一句话不是问功能,而是问“我的域名怎么解析到服务器?SSL证书要不要钱?”这种基础概念一旦模糊,后续的… · 2026/9/27 2:33:08

北京,这座物以稀为贵的城市,真的适合我吗?
北京,这座物以稀为贵的城市,真的适合我吗?

一个从沧州小县城来北京实习的普通人,写下的一些心里话。来北京之前,我对这座城市是有滤镜的。首都、中关村、北大、互联网大厂、无数人的梦想……作为一个从小县城出来的人,我一直觉得,北京这种地方,是"闯一闯&q… · 2026/9/27 2:32:56

珠海网站建设的公司哪家好新手入门
珠海网站建设的公司哪家好新手入门

珠海网站建设公司哪家好?避开被黑挂马坑的实战复盘 昨晚11点,客户电话打爆了我的手机,声音都在抖。 网站首页突然弹出一堆博彩广告,后台登录不了,百度一搜全是黑链。 那一刻你才明白, 网站被黑挂马不知道怎么办 ,才是建站最恐怖的噩梦。… · 2026/9/27 2:32:49

YOLOv8植物叶片检测实战:从LabelMe数据转换到边缘部署避坑指南
YOLOv8植物叶片检测实战:从LabelMe数据转换到边缘部署避坑指南

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

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码