EasyExcel实战:解决Apache POI内存溢出,高效读写Excel

发布时间:2026/10/13 1:19:46

EasyExcel实战:解决Apache POI内存溢出,高效读写Excel 1. 项目缘起为什么Java操作Excel会选EasyExcel相信每个Java后端多少都碰过读写Excel的需求固定格式报表导出、批量导入业务数据、定时任务生成对账文件各种场景绕不开操作Excel。早些年我的做法是直接用Apache POI去写但遇到的麻烦相当一致导出几万行数据时内存直接飙升动不动就OOM调了JVM参数也救不回来代码写起来也啰嗦光一个单元格样式就能堆出三五十行代码。后来接触到阿里巴巴开源的EasyExcel才发现原来Java操作Excel可以做得这么清爽从工具选型到日常使用这套方案我用到现在几乎成为了所有项目的标配。EasyExcel本质上是对Apache POI的轻量封装但它没有停留在“简化API”这一层而是把读写模式做了重构。写Excel时支持模型化映射一行注解就能定义列名、顺序、宽度、日期格式读Excel时走的是SAX事件解析模式不是把整个工作簿一次性加载进内存而是逐行解析触发回调几百万行的文件也能稳定处理。这一点在实际生产中价值极大服务器内存本来就宝贵用POI加载一个50MB的Excel可能要占用1GB堆内存换成EasyExcel做流式读取内存占用可能只有几十MB。实话实说EasyExcel不是万能的动画图表、复杂透视表、跨Sheet合并报表这种高级玩法它搞不定该上POI的场景还得上POI。但如果你只需要做“把Java对象列表变成Excel文件”和“把Excel文件变成Java对象列表”这两件事它几乎是无脑最优解。本文所有代码示例基于EasyExcel 3.x版本建议读者使用3.1.0以上版本后续内容我会把依赖引入、读写实操、高级特性和踩坑记录全部串起来讲尽量保证照着做就能跑通。2. 选型背后EasyExcel到底解决了什么痛点2.1 Apache POI模式下那些让人头疼的问题在讲EasyExcel之前我觉得有必要先复盘一下直接用POI的体验不然你理解不了为什么EasyExcel的设计思路如此重要。POI有两种经典操作模式HSSF对应Excel 2003的.xls格式XSSF对应Excel 2007以上的.xlsx格式。XSSF用起来倒是直观拿Workbook对象去创建Sheet、Row、Cell但问题就在这个Workbook对象上——它默认会把整个文件的XML结构全部解析进内存构建成一张巨大的对象树。你想想一个十万行、每行二十列的Excel文件底层XML里可能要拆出上百万个节点每个节点又有各种属性、风格引用这些全部塞进堆内存量级迅速膨胀。我记忆里最惨的一次项目上线前导出一份六十万行左右的业务数据报表测试环境直接OOM日志里报的是堆内存溢出。后来加了-Xmx2g参数依然不稳定因为POI对内存的消耗不是线性增长的某些操作还会复制出一份新的对象树峰值瞬间翻倍。就算内存撑住了解析速度也慢用户一听说生成文件要等十几秒甚至半分钟体验直接崩塌。2.2 EasyExcel的核心设计逻辑从DOM到SAXEasyExcel解决内存问题的核心思路是抛弃了POI的DOM式一次性加载改用SAX式流式解析。这个过程可以简单类比成看一本厚字典POI是先把整本字典复制一遍放桌上想看哪页翻哪页快是快但先把桌子占满了EasyExcel是按顺序一页一页地翻翻完一页就处理一页处理完就丢到一边桌上永远只留两三页纸。放到具体实现上EasyExcel读取.xlsx文件时底层解析器只关注Excel里那个被压缩的sheetXML文件流每次解析出一个单元格的数据立刻通过分析器通知Listener回调方法当前行的数据用完即可被GC回收。写入方向同理EasyExcel采用分批写入、即时刷盘的策略。拿到一个待导出的对象列表后它并不是把所有数据先翻译成内存里的单元格对象再整体落盘而是每攒够一定批量的数据就写成一个小数据块内存水位始终维持在较低水平。官方做过的压测里用EasyExcel写一百万个数据行的内存占用比POI低一个数量级这个提升在实际生产环境里是完全能感知到的。可能有人会觉得这个原理听懂了但跟我写代码有什么关系关系很大理解了推导过程你就能明白为什么读取大数据量文件必须用Listener模式、为什么写文件要控制批量大小后面各个坑的解决思路全都是从这个核心逻辑延伸出来的。3. 上手实操依赖引入、快速写入与注解模型3.1 Maven依赖引入与版本选择EasyExcel的坐标是com.alibaba:easyexcel在Maven中央仓库直接可用。我这几年用下来建议直接上3.1.x之后的新版本。3.0以前的包名和API设计跟现在差异比较大网上很多老教程里的“读Excel用AnalysisEventListener、写Excel用ExcelWriter”那套写法在新版本里虽然思路还在但很多类名、方法签名已经变了对着老教程写新版本代码会处处碰壁。dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency这个依赖会传递引入POI的3.17版本等底层库一般情况下不需要自己再额外声明POI坐标。需要注意的是如果项目里其他地方已经引入了POI要留意版本冲突最好统一对齐到EasyExcel传递进来的版本我遇到过因为手动把POI升级到4.x导致EasyExcel底层API签名不匹配的情况报错非常奇怪排查起来很费劲后来统一回归到依赖树里那一版就正常了。3.2 用注解定义Excel模型EasyExcel最舒服的用法是注解模型映射。你定义一个POJO字段上加几个注解写代码时把对象列表直接扔给EasyExcel列名、列顺序、单元格样式它全都自己处理。我日常用的注解大概就这几个ExcelProperty、ColumnWidth、ContentRowHeight、HeadRowHeight、DateTimeFormat、NumberFormat。Data public class UserExportVO { ExcelProperty(用户ID) private Long userId; ExcelProperty(用户名) private String username; ExcelProperty(value 年龄, order 3) private Integer age; ColumnWidth(20) ExcelProperty(value 创建时间) DateTimeFormat(yyyy-MM-dd HH:mm:ss) private Date createTime; ColumnWidth(15) ExcelProperty(账户状态) private String statusDesc; }这里有两个细节值得注意。第一个ExcelProperty里的value就是表头名称不传value的话会用字段名当表头但Java的驼峰字段名直接当表头很难看所以我一般都会显式定义。第二个字段顺序默认就是表头列的顺序写代码时不需要刻意调整字段声明顺序的话也可以通过order属性控制负数可以从最右边开始排这个用起来很灵活。3.3 一行代码导出Excel文件模型定义好之后导出写起来简单到让人怀疑是不是漏了什么。核心API就是EasyExcel.write()方法链指定文件路径、实体类型、Sheet名然后调用sheet()和doWrite()传数据。ListUserExportVO userList userService.listExportData(); String fileName /tmp/user_export.xlsx; EasyExcel.write(fileName, UserExportVO.class) .sheet(用户列表) .doWrite(userList);就这四行一个带表头、带数据的Excel就生成好了。如果数据量特别大几十万条那种建议分批写避免一次性把全量List堆在内存里。分批写法也很简单用ExcelWriter配合WriteSheet循环写入String fileName /tmp/big_user_export.xlsx; ExcelWriter excelWriter EasyExcel.write(fileName, UserExportVO.class).build(); WriteSheet writeSheet EasyExcel.writerSheet(用户列表).build(); // 每1万条写一次 for (ListUserExportVO batch : batchListProvider()) { excelWriter.write(batch, writeSheet); } excelWriter.finish();有人可能会问如果我不想定义VO类只想动态生成一批列怎么办EasyExcel也支持免模型的方式直接在writerSheet()方法里传入表头List数据用ListListString表示代码大概长这样ListListString head Arrays.asList( Arrays.asList(姓名), Arrays.asList(年龄) ); ListListString rows new ArrayList(); rows.add(Arrays.asList(张三, 25)); EasyExcel.write(/tmp/dynamic.xlsx) .head(head) .sheet(动态数据) .doWrite(rows);这种动态头写法适用于列结构不固定的场景比如用户自定义导出字段的报表系统。缺点也很明显没有类型转换能力日期、数字全都是字符串输出并且无法使用注解提供的格式化能力所以能用模型映射还是优先用模型映射。3.4 写入场景的经验心得用EasyExcel写文件我摸索出几条实操经验导出的文件流直接响应给前端下载时不要用FileOutputStream用ServletOutputStream传给EasyExcel.write()更稳妥文件不必落地。但要注意设置响应头Content-Disposition并且文件名做URL编码否则浏览器下载中文文件名会乱码。导出的Excel在Windows下打开偶尔会提醒“文件已损坏”排查过几次发现是文件名里带了冒号、问号等非法字符或者文件路径中文编码出了问题。Windows对文件名校验很严格生成文件名时最好过滤掉\ / : * ? |这几个字符。doWrite()之后不需要手动关闭流EasyExcel内部已经处理了流的关闭。但用ExcelWriter手动控制批量的场景一定记得调用finish()否则文件写不完整这个坑我踩过一次排查了半天才发现是没调finish导致文件缺失尾部数据。4. 读取进阶大数据量场景下的Listener模式4.1 默认read方法的内存隐患很多人第一次用EasyExcel的EasyExcel.read()方法时容易照葫芦画瓢地写出这样的代码ListUserImportVO list new ArrayList(); EasyExcel.read(/tmp/user_import.xlsx, UserImportVO.class, new AnalysisEventListenerUserImportVO() { Override public void invoke(UserImportVO data, AnalysisContext context) { list.add(data); } Override public void doAfterAllAnalysed(AnalysisContext context) { // 全部解析完成 } }).sheet().doRead();这样写小白也能跑通小文件完全没问题。但注意核心设计逻辑是流式解析invoke方法每解析一行就会回调一次如果你只是把每一行都塞进ArrayList其实相当于把整个Excel数据又从流式模式搬回了全量内存模式。文件小无所谓一旦到了几十万行甚至上百万行内存压力立刻回来了。4.2 用Listener配合分批落库正确姿势是让invoke方法像流水线上的工人一样每接到一批货就处理掉、清空暂存区不让数据积压。最常见的用法就是分批写入数据库攒到一定数量批量插入然后清掉List让GC回收掉这部分对象。public class UserImportListener extends AnalysisEventListenerUserImportVO { private static final int BATCH_COUNT 500; private final ListUserImportVO cachedList new ArrayList(BATCH_COUNT); private final UserService userService; public UserImportListener(UserService userService) { this.userService userService; } Override public void invoke(UserImportVO data, AnalysisContext context) { cachedList.add(data); if (cachedList.size() BATCH_COUNT) { saveBatch(); cachedList.clear(); } } Override public void doAfterAllAnalysed(AnalysisContext context) { // 最后不足一批的数据也要处理 if (!cachedList.isEmpty()) { saveBatch(); } } private void saveBatch() { userService.saveBatch(cachedList); } }使用方式变成String fileName /tmp/user_import.xlsx; EasyExcel.read(fileName, UserImportVO.class, new UserImportListener(userService)) .sheet() .doRead();数据入库后Excel文件本身可以立即删除内存里同时存活的也就是最多500行数据对象这个内存水位在任何服务器上都没压力。个人建议BATCH_COUNT不要设得过大500到1000比较合适数据库批量插入的一次性能和数据对象的内存占用都能兼顾。设置到5000甚至更多的话单次插入耗时变长而且如果库里字段多、索引多批量SQL过大容易触发数据库的连接超时或锁等待问题。4.3 读取时的空行、错误行与部分字段读取实际业务文件通常不是干干净净的可能中间夹着空行、表头下混着汇总行、某些单元格明明是数字却填成了文本。EasyExcel自然能把数据映射到VO但空行默认是不会回调invoke的除非你显式设置ignoreEmptyRow(false)。合并单元格、公式单元格的处理也要心里有数公式单元格易读的是公式本身而不是计算结果如果业务要求读取公式结果需要在模板里把公式转成静态值再导入。只读取Excel里特定几列也是常见需求。模型类里不加注解的字段或者用index属性指定想读取的列号就能实现按列抽取Data public class UserPartialVO { ExcelProperty(index 1) private String username; ExcelProperty(index 3) private Date createTime; }这样即使原Excel有二十列也只解析第2列和第4列的数据解析速度更快模型也更清爽。但这里有个隐患index写死之后如果上游模板列结构调整过数据就会串列所以使用前一定要确认模板列的稳定性或者在导入时加上列头校验逻辑。4.4 读取方向的经验总结数据导入这件事线上最容易出问题的其实不是EasyExcel本身而是上游文件的不可控性。我总结了一套比较稳妥的导入处理链路读取阶段先用EasyExcel解析成VO简单做一行级的格式校验比如必填字段为空、手机号正则等收集错误行号和错误原因校验通过的行进入批量入库阶段导入完成后统一返回一个“成功X条、失败Y条 错误明细文件下载链接”的结果。这样用户拿到手的是清晰的反馈而不是一个中途崩掉的异常堆栈。错误明细文件最好也用EasyExcel生成用List里的失败原因和原始数据拼成一个导出VO对用户来说查起来非常直观。5. 高级玩法模板填充、动态头与自定义转换5.1 基于模板的报表填充有些导出场景不是简单的“表头数据行”而是固定格式的红头文件、复杂表头的统计报表。这种场景最合适的方案是提前做好一个Excel模板里面预留好填充的占位符然后用EasyExcel的填充功能把数据灌进去。模板里写{name}表示填充单个变量写{.list}表示填充列表数据列表填充时还有{list}这种横向填充的用法可以把一行数据填充到整行多个单元格中。填充代码很简洁String templatePath /templates/report_template.xlsx; String outputPath /tmp/report_output.xlsx; MapString, Object data new HashMap(); data.put(title, 2024年度用户增长报告); data.put(generateDate, new Date()); data.put(list, userDataList); EasyExcel.write(outputPath) .withTemplate(templatePath) .sheet() .doFill(data);使用模板填充有个要点模板里的占位符只能写在单元格的文本内容中不能跨单元格。如果模板里某个单元格写成{name}{date}这种组合填充时会被视为一个整体占位符而匹配失败。模板制作时尽量每个占位符独占一个单元格格式也提前在模板里定好填充出来的报表样式就是模板的样式这样交付出来的文件维持了原本的颜值。5.2 动态合并单元格与自定义拦截器EasyExcel导出普通列表容易但有合并单元格需求时就要考虑拦截器了。比如导出一份“每个用户的多条订单记录”期望相同用户ID的单元格跨行合并可以在写出时注册一个CellWriteHandler来动态控制合并行为。public class UserCellMergeHandler implements CellWriteHandler { private final int[] mergeColumnIndexes; private final int mergeRowIndex; public UserCellMergeHandler(int mergeRowIndex, int... mergeColumnIndexes) { this.mergeRowIndex mergeRowIndex; this.mergeColumnIndexes mergeColumnIndexes; } Override public void afterCellDispose(CellWriteHandlerContext context) { // 这里拿到当前Sheet、当前行的单元格判断相邻行的相同列值是否一致 // 如果一致则调用Sheet.mergeRegions扩展合并区域 } }需要说明的是EasyExcel的写拦截器其实是在POI的写操作上做回调钩子因此合并区域的最终实现方式还是借助POI的addMergedRegionUnsafe或者CellRangeAddress。我对合并单元格的建议是能不在代码里合成就别在代码里合成数据源提前排好序、把相同分组的记录相邻排列合并逻辑会简单很多。如果数据本身是乱序的合并出来的表格会非常混乱用户看到大概率会直接打回。5.3 自定义类型转换器注解里内置的DateTimeFormat和NumberFormat能应付绝大多数常见类型但业务场景往往有着奇怪的格式需求。比如数据库里存的是时间戳Long导出到Excel希望展示成2024-05-20 13:14:00这种标准字符串又比如枚举类型希望在导出时显示描述文本、导入时能把描述文本反解析成枚举值。这种场景就需要自定义Converter。EasyExcel的Converter接口核心就两个方法convertToJavaData和convertToExcelData。前者是把Excel单元格数据转成Java对象用于读取场景后者是把Java对象转成Excel单元格数据用于写入场景。public class StatusConverter implements ConverterStatusEnum { Override public ClassStatusEnum supportJavaTypeKey() { return StatusEnum.class; } Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } Override public StatusEnum convertToJavaData(ReadCellData? cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return StatusEnum.fromDesc(cellData.getStringValue()); } Override public WriteCellData? convertToExcelData(StatusEnum value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return new WriteCellData(value.getDesc()); } }使用方式是在字段注解上加converter属性ExcelProperty(value 状态, converter StatusConverter.class) private StatusEnum status;这个功能很实用导出的文件里看到的是“启用/禁用”读回来能自动映射成Java枚举全程不会暴露代码里的魔法值。6. 避坑指南高频问题与排查实录6.1 读入数字变成科学计数法这是实际使用中最常见的问题。Excel单元格里输入身份证号、手机号这类长数字时如果单元格格式是“常规”Java读取出来往往带科学计数法比如1.234567890123456E18。解决办法有几个方向模型字段类型不要用Long或Integer用String接收。EasyExcel对字符串类型的单元格原样读取能保留原始数字文本。这个方案最简单推荐优先用。如果必须用数值类型并且数字长度在Java的Long范围内可以给NumberFormat注解指定格式为#来去除小数点尾巴但科学计数法是否彻底消失取决于具体数据。最稳妥的是在数据源头控制要求上游导出Excel时把长数字列设置为“文本”格式。这个问题源头堵住后面全链路都省心。6.2 日期字段读出来是数字Excel内部对日期的存储机制是“日期序列号”比如45292这种整数单元格格式决定它显示成2024-01-01还是别的样子。EasyExcel读取时如果字段是Date类型并且没加DateTimeFormat在某些格式不规范的模板里可能拿到的是一个数值需要手动转换。解决办法很简单模型类日期字段上加DateTimeFormat(yyyy-MM-dd HH:mm:ss)Excel就会按照指定格式解析日期字符串。更隐蔽的坑是Excel单元格里的日期格式跟注解读取格式不一致比如模板显示的是2024/01/05但你指定的是yyyy-MM-dd这会导致解析失败或拿到null。对齐格式是这类问题唯一的根治手段拿到模板后先确认它的显示格式再定注解。6.3 大文件读取卡顿或OOM首先确认是不是用了Listener模式并分批处理如果是动态自定义表头、多Sheet场景可能需要在doRead()之前先调用ExcelReader去扫描Sheet结构。大文件OOM还有一个常见原因是JVM堆太小EasyExcel虽然已经做到流式解析但文件非常大几百MB时还是需要一定的堆空间建议-Xmx至少给1GB以上这个量级压测下来一般都能稳定。另外如果文件是.xls后缀Excel 2003格式EasyExcel不支持流式读取会退回到POI老模式内存本身就很高。遇到这种情况最干净的办法是让上游把文件转成.xlsx再导入或者写一个小工具先行转换。非要直接读取的话内存限制很难绕开。6.4 写文件时表头重复或乱序表头重复这个坑多数是因为同一个模型类被用于多个Sheet的写出而WriteSheet缓存了表头。每次构建WriteSheet尽量调用EasyExcel.writerSheet(sheet名)新建不要复用同一个WriteSheet对象去写不同模型。表头乱序则基本是字段声明顺序导致的虽然order属性可以控制但最省心的做法还是直接按需要的顺序声明字段。6.5 代码里修改了字段但测试环境还是旧表头这是缓存问题EasyExcel在3.x版本里自带一些模板和头相关的缓存修改模型类后旧的编译产物或临时文件没有清理。遇到这种情况把target目录清理掉重新编译一般就能解决。更彻底的做法是排查项目里是否引入了多个不同版本的EasyExcel依赖依赖树里有冲突版本时类加载器加载到的旧类会遮蔽新代码。6.6 导出文件在Mac/WPS打开正常Excel打不开不同Excel实现之间的容错率差别很大。WPS对很多不规范的文件结构容忍度高但微软Excel校验严格这通常意味着生成的.xlsx文件结构有瑕疵。排查方向上先确认代码里是否正确调用了finish()或doWrite()完成了收尾再检查是否有多个Writer实例并发写同一个文件如果用了ExcelWriter的手动批量模式finish()之后不要再有任何写操作否则文件流状态错乱。模板填充场景则要检查模板本身是不是用不同版本的Excel/WPS反复编辑保存过建议用固定工具制作模板并减少二次编辑。7. 整理的速查表日常调参最容易踩的配置项把常用配置梳理成一张速查表开发时候对着查比翻文档高效得多场景关键配置备注读取时忽略空行.ignoreEmptyRow(false)默认true表示直接跳过空行自动关闭流.autoCloseStream(true)默认true手动管理流的场景需要关掉它读取特定Sheet.sheet(Sheet1)/.sheet(0)按名字或下标取Sheet读首个非空Sheet.sheet()默认取第一个非空Sheet只读取部分行.headRowNumber(1)表示前1行是表头不参与数据处理同步读取非流式EasyExcel.read(...).sheet().doReadSync()返回List简单场景可用大文件慎用写文件是否包含表头.needHead(false)默认true导出纯数据可关闭模板填充.withTemplate(模板路径)配合doFill()使用字符集.charset(StandardCharsets.UTF_8)读取csv时需要格外注意这些配置项本质上对应的是EasyExcel的ReadWorkbook、ReadSheet、WriteWorkbook、WriteSheet这四类内部属性对象记住这一点遇到陌生参数时思路会更清晰读相关找Read前缀写相关找Write前缀。8. 最后的实战建议在多个生产系统里用了EasyExcel这几年最大的感受是“简单是它的表象节制才是它的内核”。它把操作Excel的手续费降到了最低但真正决定稳定性的还是使用姿势写大数据量时想清楚批量策略读文件时时刻记住流式解析而不是全量累积遇到异常格式时先在上游规范数据而不是在代码里硬扛。我自己现在的习惯是任何涉及Excel导入导出的模块模型类、Listener、工具方法都会单独抽一个包里维护后续换字段、加格式都集中改动不会散落在业务代码里。如果你要把这套玩法再向前推一步可以关注EasyExcel官方后续版本对低版本POI的升级、对大数据量写入性能的优化也可以研究一下结合模板能力做“批量生成多个报表文件并打包下载”的场景。实际操作中我试过把Excel工具类和文件存储服务解耦导出文件直接写到对象存储然后给前端返回一条下载链接几十万人并发的下载高峰期页面也就毫秒级响应比后端转发文件流舒服得多。这个方向很值得在实际项目中尝试希望上面的内容对你着手使用EasyExcel能有些实实在在的帮助。
延伸阅读

更多相关文章

2026/10/13 1:14:45

小程序自定义数字键盘:从原理到可复用组件实现

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

2026/10/13 1:14:45

支付宝群成员获取实战:从权限申请到分页游标踩坑全解析

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

2026/10/13 1:14:45

VS编译LAStools工具全解析:从源码到可执行文件

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

2026/10/13 2:14:48

LeetCode 19:删除链表倒数第 N 个节点,双指针与虚拟头节点详解

LeetBook 上这道删除链表倒数第 N 个结点的题,我前前后后教过不少人和自己重写过好几遍,看着简单,其实特别容易在边界条件上翻车。写这篇东西的目的很纯粹:把这道题的前因后果、两种主流解法、还有我踩过的坑全部摊开来聊一遍&…

2026/10/13 2:14:48

Spring Boot Starter实现Jackson @JsonView分页字段过滤

1. 为什么我非要做一个 View Extension Starter做后端接口的人基本都经历过这么个事:同一个用户对象,列表接口只想给 App 返回 id、name、avatar,管理后台接口却要把 email、手机号、角色全带上。最粗暴的做法是写两个 DTO、两套 Service、两…

2026/10/13 2:14:48

Win11下西门子工业以太网卡导致无法关机?排查与解决指南

做自动化改造的同行应该都有体会,设备能正常跑不等于系统健康,真正折磨人的往往是那些不影响生产、但能让你反复纠结的系统级小毛病。前两天我就遇到一台装有西门子工业以太网通讯网卡的工控机,系统是 Windows 11 专业版,生产用的…

2026/10/13 2:14:48

Oracle rownum与rowid原理、陷阱及分页优化实战

1. 为什么刚学Oracle的人总在rownum上栽跟头&#xff1f;我带过不少刚从MySQL或PostgreSQL转过来的开发者&#xff0c;他们第一次写分页SQL时&#xff0c;几乎无一例外地写出这样的语句&#xff1a;SELECT * FROM employees WHERE rownum < 10 ORDER BY salary DESC;然后一脸…

2026/10/13 2:14:48

Socket网络编程从入门到实战:TCP/UDP原理、粘包半包与高并发调优

做网络编程&#xff0c;第一关一定是Socket。不管你是写Web服务、IM聊天工具&#xff0c;还是搞设备接入、数据采集&#xff0c;Socket就是那层绕不开的底子。我最早碰Socket是在调一个局域网传输工具的收发速度问题&#xff0c;那时对着报错一头雾水&#xff0c;后来把协议栈、…

2026/10/13 2:09:48

一文讲透Markdown代码语法:行内代码、代码块与渲染生态

1. 行内代码与代码块&#xff1a;先搞清楚你写的到底是哪种"代码"很多人以为 Markdown 的代码语法就是"打三个反引号"&#xff0c;结果真到写技术文档、配置说明、公众号排版的时候&#xff0c;一粘贴就露馅&#xff1a;反引号失效、高亮乱七八糟、代码块里…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”&#xff0c;而是它给出一段看起来合理的说明&#xff0c;程序却从中猜错优先级。通俗做法是&#xff1a;要求模型只交 JSON&#xff08;JavaScript Object Notation&#xff0c;轻量数据格式&#xff09;&#xff0c;再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里&#xff0c;有A同学跑来问我&#xff1a;选什么毕设题目最稳妥&#xff0c;既能让评审老师觉得工作量够&#xff0c;又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友&#xff0c;在看完一堆“Hello World”和基础组件之后&#xff0c;大概率都会撞上同一堵墙&#xff1a;StatefulWidget 里那堆 initState、build、dispose 方法&#xff0c;到底什么时候被调用&#xff1f;为什么顺序是那样&#xff1f;在里面到…

2026/10/13 0:04:41

基于Triton部署YOLO目标检测:从训练脚本到线上服务

简介&#xff1a;这份资源面向希望将目标检测模型落地到生产环境的深度学习开发者与算法工程师&#xff0c;聚焦于用 Triton Inference Server 部署 YOLOv9 的完整实战方案&#xff0c;解决从训练权重到线上推理服务之间的工程化难题。压缩包共 16 个文件&#xff0c;约 886KB&…

2026/10/13 0:04:41

手把手教你云端部署Qwen-Image-2.1:从GPU选型到在线文生图服务

Qwen-Image-2.1 这个开源的图像生成模型&#xff0c;最近在内容创作和 AI 应用圈子里热度相当高。它属于通义 Qwen 系列里的视觉生成分支&#xff0c;让我眼前一亮的是它对中文长文本的理解能力——以前用那些老方案生成带中文文字的图&#xff0c;十个里九个是“乱码印刷体”&…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑