news 2026/8/3 23:30:43

EasyExcel自适应列宽与行高:原理、实现与性能优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EasyExcel自适应列宽与行高:原理、实现与性能优化

1. 项目概述:为什么我们需要“自适应”的Excel导出?

做后端开发,尤其是涉及报表导出的场景,用EasyExcel的朋友肯定不少。它确实好用,API简洁,内存占用低,避免了传统POI处理大文件时的OOM噩梦。但不知道你有没有遇到过这样的尴尬:导出的Excel文件,打开后单元格里的文字被“无情”截断,或者因为内容过长导致整行高得离谱,又或者因为列太窄而不得不手动双击调整列宽。用户拿到这样的报表,第一印象就打了折扣,体验非常不友好。

这就是我们今天要解决的痛点:让EasyExcel导出的Excel能“智能”地根据单元格内容,自动调整行高和列宽,生成一个看起来专业、用起来舒服的表格文件。这个需求听起来简单,但EasyExcel本身并没有提供“一键自适应”的魔法方法。它更专注于高效的数据读写,样式和格式需要开发者自己精细控制。所谓的“自适应”,本质上是我们通过计算内容尺寸,然后动态设置行高和列宽来实现的。

网上能找到的很多教程,要么只讲了列宽自适应,要么只调了行高,而且常常忽略了中英文混排、换行符、字体大小、甚至合并单元格这些复杂情况。今天,我就结合自己多次“踩坑”的经验,给你带来一份从原理到实现的“保姆级”解决方案。无论你是要导出简单的数据列表,还是复杂的统计报表,这套方法都能帮你搞定。

2. 核心思路拆解:自适应背后的计算逻辑

在动手写代码之前,我们必须搞清楚Excel和EasyExcel处理样式的逻辑,以及“自适应”到底在计算什么。这能帮你理解后续每一步操作的原因,而不是机械地复制粘贴。

2.1 Excel的度量单位与EasyExcel的映射关系

Excel中,列宽的默认单位是一个“字符宽度”。这个“字符”指的是在“常规”样式(11号宋体)下,单元格能显示的数字0的个数。比如,列宽设置为10,意味着大约能显示10个“0”。而行高的单位是“磅”(Point),这是一个绝对长度单位,1磅约等于1/72英寸。

在EasyExcel(底层是Apache POI)中,我们设置样式时,需要与这些单位打交道:

  • 列宽(ColumnWidth):可以直接设置一个整数值,对应Excel的字符宽度。但POI内部处理时,这个值会乘以256。所以,sheet.setColumnWidth(columnIndex, width * 256)中的width就是我们理解的字符数。
  • 行高(RowHeight):在POI中,行高以“缇”(twip)为单位,1缇 = 1/20磅。所以,设置行高时,我们通常用row.setHeightInPoints(height)来直接设置磅值,POI会帮我们做转换。

2.2 如何计算内容的“宽度”和“高度”?

这是自适应的核心。我们不能凭空猜测该设多宽多高,必须根据实际要渲染的文本内容来计算。

  1. 计算列宽:关键是要知道最长的内容。我们需要遍历某一列的所有数据(或表头),找出其中字符数最多的那个。但是,简单比较字符长度是不准确的,因为中文字符和英文字符的宽度不同。一个常见的经验法则是:一个中文字符的宽度大约等于两个英文字符。因此,计算有效宽度时,需要遍历字符串,区分中英文。更精确的做法是使用FontMetrics(字体度量)来获取每个字符在特定字体下的实际像素宽度,但这在无头环境(服务器)下比较复杂。我们通常采用一种折中但效果不错的“字符系数”法。

  2. 计算行高:行高取决于内容的行数字体大小。如果单元格内容包含换行符\n,或者因为列宽不足导致文本自动换行(WrapText),那么内容就会占据多行。每一行的高度至少要和字体大小相匹配,还要加上一些行间距。在Excel中,当“自动换行”开启时,行高会自动调整。但在EasyExcel中,我们需要自己计算:行高 ≈ (字体磅值 * 行数) + 额外边距。行数可以通过内容总字符数 / 每行预估字符数来估算,更准确的是用BreakIterator或按换行符分割。

2.3 方案选型:何时计算?如何应用?

计算出来了,什么时候设置呢?这里有两个主要策略:

  • 策略一:提前计算,一次性设置(推荐用于数据量已知的场景)。在数据写入完成后,遍历所有已写入的单元格,计算每一列的最大宽度和每一行的最大高度,然后统一调用sheet.setColumnWidthrow.setHeightInPoints进行设置。这种方法逻辑清晰,但需要等所有数据就位。
  • 策略二:在写入过程中动态调整(流式处理或大数据量场景)。通过实现EasyExcel的CellWriteHandler单元格写入处理器,在每次写入一个单元格时,更新该列的最大宽度和该行的最大高度记录。在所有数据写入完毕后,再应用最终的计算结果。这种方法更灵活,适合边读边写的流式导出。

我们今天的教程将以策略一为主进行讲解,因为它更容易理解,并且在大多数导出场景(数据量在数万条以内)下性能完全足够。在后面的高级技巧里,我们会简要介绍策略二的实现思路。

3. 基础实现:一步步构建自适应处理器

理论清楚了,我们开始写代码。我们将创建一个工具类AutoColumnWidthAndHeightStyleStrategy,它实现了EasyExcel的CellWriteHandler接口,以便在写入过程中拦截并处理样式。

3.1 项目依赖与环境准备

首先,确保你的pom.xml中引入了EasyExcel的依赖。建议使用较新的版本。

<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> <!-- 请使用最新稳定版 --> </dependency>

3.2 核心工具类:宽度与高度计算器

我们先创建一个计算工具类,它负责最核心的数学问题。

import org.apache.poi.ss.usermodel.Font; import java.util.HashMap; import java.util.Map; public class CellSizeCalculator { // 缓存字体度量信息,避免重复创建(简化版,实际服务器环境可能需要不同处理) private static final Map<String, Float> FONT_WIDTH_CACHE = new HashMap<>(); private static final int DEFAULT_COLUMN_WIDTH = 15; // 默认列宽(字符) private static final float DEFAULT_ROW_HEIGHT_IN_POINTS = 15.0f; // 默认行高(磅) private static final float EXTRA_ROW_HEIGHT_MARGIN = 4.0f; // 行高额外边距(磅) private static final float CHINESE_CHAR_WIDTH_FACTOR = 1.8f; // 一个中文字符相当于英文字符的宽度系数(经验值) private static final float ENGLISH_CHAR_WIDTH_FACTOR = 1.0f; /** * 计算字符串在Excel中占用的近似宽度(字符数) * @param content 单元格内容 * @param fontSize 字体大小(磅) * @return 建议的列宽(字符数) */ public static int calculateColumnWidth(String content, int fontSize) { if (content == null || content.isEmpty()) { return DEFAULT_COLUMN_WIDTH; } float totalWidth = 0.0f; // 遍历每个字符 for (char c : content.toCharArray()) { if (isChinese(c)) { totalWidth += CHINESE_CHAR_WIDTH_FACTOR; } else { totalWidth += ENGLISH_CHAR_WIDTH_FACTOR; } } // 根据字体大小微调:字体越大,需要的列宽也等比增加 float fontScale = fontSize / 11.0f; // 基准字体为11磅 int suggestedWidth = (int) Math.ceil(totalWidth * fontScale); // 加上一些缓冲,并设置最大最小值 suggestedWidth = Math.max(suggestedWidth + 2, 5); // 最小5个字符,缓冲2个字符 suggestedWidth = Math.min(suggestedWidth, 100); // 最大100个字符,防止过宽 return suggestedWidth; } /** * 计算字符串在Excel中占用的近似高度(磅) * @param content 单元格内容 * @param columnWidth 该单元格所在列的宽度(字符数) * @param fontSize 字体大小(磅) * @param wrapText 是否自动换行 * @return 建议的行高(磅) */ public static float calculateRowHeight(String content, int columnWidth, int fontSize, boolean wrapText) { if (content == null || content.isEmpty()) { return DEFAULT_ROW_HEIGHT_IN_POINTS; } int lines = 1; // 至少一行 if (wrapText) { // 估算在给定列宽下,内容会分成几行 float effectiveCharWidth = calculateColumnWidth(content, fontSize) / (float) content.length(); // 平均每个字符占宽 // 列宽能容纳的字符数(估算) int charsPerLine = (int) (columnWidth / effectiveCharWidth); if (charsPerLine <= 0) charsPerLine = 1; // 计算行数:按换行符分割 + 宽度换行 String[] splitByNewLine = content.split("\n"); for (String line : splitByNewLine) { lines += Math.ceil((float) line.length() / charsPerLine); } lines = Math.max(lines, splitByNewLine.length); // 取两者最大值 } else { // 如果不换行,只看显式换行符 lines = content.split("\n").length; } // 行高 = 字体大小 * 行数 + 额外边距 float height = (fontSize * lines) + EXTRA_ROW_HEIGHT_MARGIN; return Math.max(height, DEFAULT_ROW_HEIGHT_IN_POINTS); } private static boolean isChinese(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION; } }

注意:这里的calculateColumnWidth方法使用的是经验系数法,在大多数情况下(字体为11pt左右,中英文混合)效果良好。对于字体差异极大或有特殊符号的场景,最精确的方法是使用java.awt.FontMetrics,但这需要图形环境。在无头服务器上,可以尝试使用org.apache.poi.ss.usermodel.Font相关方法进行更精确的测量,但复杂度会提高。我们目前的实现是一个在精度和复杂度之间取得良好平衡的实用方案。

3.3 实现自适应样式策略

接下来,我们实现核心的处理器。这里采用策略一:先写入所有数据,再统一调整。

import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*; import java.util.*; public class AutoColumnWidthAndHeightStyleStrategy implements CellWriteHandler { // 记录每一列的最大宽度(列索引 -> 最大宽度字符数) private Map<Integer, Integer> maxColumnWidthMap = new HashMap<>(); // 记录每一行的最大高度(行索引 -> 最大高度磅值) private Map<Integer, Float> maxRowHeightMap = new HashMap<>(); // 全局字体大小(假设所有数据单元格使用相同字体大小,可从样式获取更佳) private int dataFontSize = 11; // 表头字体大小 private int headFontSize = 12; // 是否启用自动换行 private boolean wrapText = true; public AutoColumnWidthAndHeightStyleStrategy() {} public AutoColumnWidthAndHeightStyleStrategy(int dataFontSize, int headFontSize, boolean wrapText) { this.dataFontSize = dataFontSize; this.headFontSize = headFontSize; this.wrapText = wrapText; } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 这个方法在单元格创建后被调用,是收集信息的好时机 Sheet sheet = writeSheetHolder.getSheet(); int rowIndex = cell.getRowIndex(); int columnIndex = cell.getColumnIndex(); String cellValue = getCellValueAsString(cell); int currentFontSize = isHead ? headFontSize : dataFontSize; // 1. 更新列宽信息 int cellWidth = CellSizeCalculator.calculateColumnWidth(cellValue, currentFontSize); maxColumnWidthMap.merge(columnIndex, cellWidth, Math::max); // 2. 更新行高信息(需要列宽,这里先记录,最后统一计算) // 我们暂时只记录内容和列索引,在afterSheetDispose中统一计算行高 // 为了简化,我们可以在这里先估算一个高度,但更准确的做法是最后根据最终列宽计算 // 此处先记录一个基础高度,最后再调整 float estimatedHeight = CellSizeCalculator.calculateRowHeight(cellValue, 15, currentFontSize, wrapText); // 先用默认列宽15估算 maxRowHeightMap.merge(rowIndex, estimatedHeight, Math::max); } @Override public void afterSheetDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder) { // 所有单元格写入完成后,统一应用自适应样式 Sheet sheet = writeSheetHolder.getSheet(); // 应用列宽 for (Map.Entry<Integer, Integer> entry : maxColumnWidthMap.entrySet()) { int columnIndex = entry.getKey(); int width = entry.getValue(); // POI设置列宽需要乘以256 sheet.setColumnWidth(columnIndex, width * 256); } // 应用行高(需要根据最终列宽重新精确计算) for (int rowNum = sheet.getFirstRowNum(); rowNum <= sheet.getLastRowNum(); rowNum++) { Row row = sheet.getRow(rowNum); if (row == null) { continue; } float maxHeightInRow = maxRowHeightMap.getOrDefault(rowNum, 0f); // 获取该行的实际最大列宽(用于更精确计算行高) int maxColWidthInRow = 0; for (Cell cell : row) { int colIdx = cell.getColumnIndex(); maxColWidthInRow = Math.max(maxColWidthInRow, maxColumnWidthMap.getOrDefault(colIdx, 15)); } // 重新计算该行所有单元格中所需的最大高度 float finalHeight = maxHeightInRow; // 这里可以做一个更精确的二次计算,为了示例我们使用之前估算的 // 确保行高不小于默认值 if (finalHeight < 15.0f) { finalHeight = 15.0f; } row.setHeightInPoints(finalHeight); } // 可选:为所有单元格设置自动换行样式 if (wrapText) { CellStyle wrapStyle = sheet.getWorkbook().createCellStyle(); wrapStyle.setWrapText(true); for (Row row : sheet) { for (Cell cell : row) { CellStyle existingStyle = cell.getCellStyle(); // 避免覆盖原有样式,可以克隆并设置 CellStyle newStyle = sheet.getWorkbook().createCellStyle(); newStyle.cloneStyleFrom(existingStyle); newStyle.setWrapText(true); cell.setCellStyle(newStyle); } } } } private String getCellValueAsString(Cell cell) { if (cell == null) { return ""; } switch (cell.getCellType()) { case STRING: return cell.getStringCellValue(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { return cell.getDateCellValue().toString(); } else { return String.valueOf(cell.getNumericCellValue()); } case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); case FORMULA: return cell.getCellFormula(); case BLANK: return ""; default: return ""; } } }

3.4 在导出时使用我们的处理器

最后,我们看看如何在写Excel时应用这个处理器。

import com.alibaba.excel.EasyExcel; import java.util.ArrayList; import java.util.List; public class ExportService { public void exportAdaptiveExcel(String fileName) { // 1. 准备数据 List<YourDataModel> dataList = getDataList(); // 2. 构建并配置写处理器 AutoColumnWidthAndHeightStyleStrategy styleStrategy = new AutoColumnWidthAndHeightStyleStrategy(11, 12, true); // 3. 写入Excel EasyExcel.write(fileName, YourDataModel.class) .registerWriteHandler(styleStrategy) // 注册我们的自适应处理器 // 可以继续注册其他处理器,比如表头样式、单元格格式等 .sheet("自适应Sheet") .doWrite(dataList); System.out.println("导出完成,文件已自适应调整行高列宽。"); } private List<YourDataModel> getDataList() { List<YourDataModel> list = new ArrayList<>(); // 模拟一些长短不一的数据 list.add(new YourDataModel(1L, "这是一个很短的名字", "这是一个非常非常长的描述字段,用来测试列宽自适应和行高自适应,因为里面包含了换行符\n这是第二行的内容。", 100.0)); list.add(new YourDataModel(2L, "用户名稍微长一点测试", "中等描述", 200.5)); list.add(new YourDataModel(3L, "短", "这里也是描述,但是不带换行。", 300.0)); return list; } } // 你的数据模型类 class YourDataModel { private Long id; private String name; private String description; private Double amount; // 构造方法、getter/setter省略... }

运行上面的代码,导出的Excel文件,其列宽会根据“姓名”和“描述”列中最长的内容自动调整,“描述”列中带换行符的单元格所在行,其行高也会自动增加,确保内容完全显示。

4. 高级技巧与避坑指南

基础功能实现了,但在实际项目中,你会遇到更复杂的情况。下面分享几个我踩过坑后总结的高级技巧和注意事项。

4.1 处理合并单元格的自适应

合并单元格是报表中的常客。自适应处理合并单元格时,需要特殊对待,因为合并区域内的内容会影响多行多列。

核心思路:在afterCellDispose方法中,判断单元格是否属于某个合并区域。如果是,在计算列宽时,需要将该内容的宽度“贡献”给合并区域所覆盖的所有列(通常是第一列)。计算行高时,也需要考虑合并区域所覆盖的所有行。

// 在 afterCellDispose 方法中补充 Sheet sheet = writeSheetHolder.getSheet(); List<CellRangeAddress> mergedRegions = sheet.getMergedRegions(); for (CellRangeAddress mergedRegion : mergedRegions) { if (mergedRegion.isInRange(cell.getRowIndex(), cell.getColumnIndex())) { // 该单元格在合并区域内 int firstColumn = mergedRegion.getFirstColumn(); int lastColumn = mergedRegion.getLastColumn(); // 将计算出的宽度应用到合并区域的首列(或其他策略,如平均分配) maxColumnWidthMap.merge(firstColumn, cellWidth, Math::max); // 行高处理类似,合并区域的行高应由其内容决定,并应用到所有合并行 break; } }

注意:合并单元格的逻辑相对复杂,一个稳健的实现可能需要遍历所有合并区域,并记录每个区域对应的内容和计算出的尺寸,最后再统一应用。上面的代码提供了一个简单的切入点。

4.2 性能优化:应对海量数据导出

当导出数据达到十万甚至百万级时,在afterCellDispose中对每个单元格进行复杂计算和Map的合并操作可能会成为性能瓶颈。

优化策略

  1. 采样计算:不必遍历所有数据行。对于列宽,可以只计算前N行(如1000行)和最后N行,因为数据的长度分布通常具有一定的代表性。对于行高,可以只对包含换行符或超长文本的行进行精确计算。
  2. 异步或分批处理:将样式计算与数据写入分离。可以先快速将数据写入一个临时文件或内存,然后启动一个后台线程或任务,读取这个文件,计算样式,再应用并生成最终文件。这需要更复杂的架构。
  3. 使用更高效的数据结构:如果列数固定,可以用数组int[] maxWidths代替Map<Integer, Integer>,访问更快。
  4. 关闭精细计算:对于超大数据量,可以提供一个开关,只进行简单的字符数统计(忽略中英文差异和字体缩放),牺牲一点精度换取速度。

4.3 字体与样式的精确测量

我们之前的“系数法”是个很好的近似。如果你对精度有极致要求,特别是在使用非标准字体或特殊符号时,可以尝试更精确的方法。

思路:利用java.awt.FontMetrics(需要图形环境或Headless模式支持)或POI的org.apache.poi.ss.usermodel.Font相关方法。POI的Workbook可以创建Font对象,但获取精确的像素宽度比较麻烦。一个变通的方法是,在初始化时,用一个已知的“测试字符串”在Excel中渲染,并记录其像素宽度,反推出一个更准确的系数。这通常需要一些前期校准工作。

// 伪代码,展示校准思路 // 在应用启动时,可以运行一次校准程序 Font font = workbook.createFont(); font.setFontName("宋体"); font.setFontHeightInPoints((short)11); // 如何获取这个font在Excel中的渲染宽度?这里是个难点。 // 一种方案是:用POI创建一个很小的临时Sheet,设置内容和字体,然后尝试获取渲染后的列宽(这通常需要渲染引擎)。 // 更实际的方案是:基于经验值,并提供配置项让开发者根据实际效果微调系数。

4.4 常见问题排查(FAQ)

在实际使用中,你可能会遇到以下问题:

Q1:导出的文件列宽还是不对,有些内容显示不全?

  • A1:首先检查你的calculateColumnWidth方法中的系数(CHINESE_CHAR_WIDTH_FACTOR)是否适合你的主要字体。如果导出的内容以数字和英文为主,可以适当调低该系数。其次,确认是否设置了wrapTexttrue,如果内容过长且未换行,即使列宽足够,也可能显示为####。最后,检查是否有合并单元格未正确处理。

Q2:设置了自适应,但打开Excel后行高列宽又变了?

  • A2:Excel本身有“自动调整列宽”和“自动调整行高”功能,但那是客户端的交互行为。我们通过POI设置的是文件本身的属性。如果打开后手动双击了列分隔线,Excel会按照当前内容重新调整。我们生成的文件在打开时,其尺寸就是固定的。你说的“变了”,可能是由于Excel默认的显示缩放比例不是100%,或者使用了不同的默认字体,这属于客户端渲染差异,很难完全避免。确保服务器和客户端使用的字体(如“宋体”、“微软雅黑”)是存在的。

Q3:处理大量数据时,内存占用很高或导出很慢。

  • A3:回顾4.2节的性能优化建议。首先确保使用的是EasyExcel的“简单”或“重复”模式写入,而不是一次性将所有数据模型加载到内存的“复杂”模式。其次,审视你的CellWriteHandler逻辑,避免在afterCellDispose中执行耗时的操作(如复杂的字符串解析、数据库查询)。将必要的计算提前或缓存。

Q4:数字和日期格式的列,自适应效果不好。

  • A4:对于数字和日期,计算宽度时不能直接用它们的toString()。例如,数字10000001E6显示长度不同,日期2023-10-012023/10/1也不同。建议在getCellValueAsString方法中,对于数字和日期类型,使用Excel格式化后的字符串进行计算,或者为这些列指定一个固定的、合理的列宽。
private String getCellValueAsString(Cell cell) { // ... 其他类型判断 case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { // 获取日期格式 DataFormatter formatter = new DataFormatter(); return formatter.formatCellValue(cell); // 返回格式化后的日期字符串 } else { // 获取数字格式 CellStyle style = cell.getCellStyle(); short formatIndex = style.getDataFormat(); String formatString = style.getDataFormatString(); // 简单处理:保留两位小数 return String.format("%.2f", cell.getNumericCellValue()); } // ... }

5. 扩展:与表头样式、单元格格式的协同

自适应的样式处理器可以很容易地与其他处理器组合使用,打造功能丰富的报表。

例如,你有一个HeaderStyleHandler用来设置表头背景色和加粗,还有一个NumberFormatHandler用来设置金额列的千分位格式。在EasyExcel中,通过链式调用registerWriteHandler注册多个处理器即可。处理器会按照注册顺序执行。

EasyExcel.write(fileName, YourDataModel.class) .registerWriteHandler(new AutoColumnWidthAndHeightStyleStrategy()) // 自适应 .registerWriteHandler(new HeaderStyleHandler()) // 表头样式 .registerWriteHandler(new CustomCellFormatHandler()) // 自定义格式 .sheet("报表") .doWrite(dataList);

注意事项:如果多个处理器都修改了单元格样式(如CellStyle),需要注意样式覆盖的问题。后注册的处理器可能会覆盖前面处理器设置的样式。通常的实践是,将样式设置(如字体、颜色、边框)和尺寸设置(行高列宽)分开。我们的自适应处理器主要关心尺寸,应尽量避免直接设置CellStyle的字体、颜色等属性,除非必要(如开启自动换行)。可以在其他处理器中创建完整的样式,自适应处理器在最后阶段只调整尺寸或合并样式。

最后,再分享一个我个人的小技巧:对于超长文本字段(如“备注”、“详细描述”),即使做了自适应列宽,也可能导致表格过宽影响打印或浏览。一个更友好的做法是固定该列的宽度为一个合理值(如50个字符),然后强制开启自动换行,并设置一个较大的初始行高。这样,内容会垂直展开,保证可读性的同时,控制了表格的整体宽度。这需要你在数据模型或处理器中,对不同字段进行差异化的策略配置。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/3 23:29:58

Notablog部署教程:3分钟将Notion博客发布到静态托管平台

Notablog部署教程&#xff1a;3分钟将Notion博客发布到静态托管平台 【免费下载链接】notablog Tell stories of your work with Notion 项目地址: https://gitcode.com/gh_mirrors/no/notablog Notablog是一款基于Notion的静态博客生成工具&#xff0c;能帮助用户快速将…

作者头像 李华
网站建设 2026/8/3 23:27:21

三相并网逆变器电流模型预测MPC控制Matlab仿真模型123(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码

三相并网逆变器电流模型预测MPC控制Matlab仿真模型123(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_文章底部可以扫码 [1]采用电压外环PI内环电流模型预测控制&#xff0c;单矢量 [2]外环稳定并网输出电压311V&#xff0c;内环通过电流模型预…

作者头像 李华
网站建设 2026/8/3 23:25:08

ttl.sh常见问题解答:解决你使用临时镜像仓库的所有疑惑

ttl.sh常见问题解答&#xff1a;解决你使用临时镜像仓库的所有疑惑 【免费下载链接】ttl.sh An anonymous & ephemeral Docker image registry 项目地址: https://gitcode.com/gh_mirrors/tt/ttl.sh ttl.sh是一个基于zot构建的匿名、临时容器镜像仓库&#xff0c;专…

作者头像 李华