ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

EasyExcel自适应列宽行高实现:基于POI的智能导出方案

EasyExcel自适应列宽行高实现:基于POI的智能导出方案 1. 项目概述为什么我们需要“自适应”的Excel导出做后端开发尤其是涉及报表导出的场景用EasyExcel的朋友肯定不少。它确实方便几行代码就能把数据变成Excel文件。但不知道你有没有遇到过这样的尴尬导出的Excel文件打开一看有的单元格文字被挤成了“#”有的又因为内容太少留了一大片空白用户还得手动去调整列宽、行高体验非常糟糕。这就是我们今天要解决的痛点让EasyExcel导出的Excel文件在打开时就能根据内容自动调整行高和列宽实现“所见即所得”的完美效果。这个需求听起来简单但EasyExcel本身并没有提供一键“自适应”的开关。它默认的列宽策略ColumnWidth和行高策略ContentRowHeight是固定的不会根据你写入的内容动态变化。所以“保姆级教程”这个前缀意味着我们要从原理出发手把手带你绕过EasyExcel的“舒适区”深入到Apache POI的底层EasyExcel基于POI封装去实现一个真正智能、好用的自适应导出方案。这不仅仅是调个参数更是对POI SXSSFSheet操作的一次深度实践。接下来我会结合我踩过的坑和优化经验把完整的实现路径、核心代码以及那些官方文档里不会写的细节毫无保留地分享给你。2. 核心原理与方案选型为什么不能直接设置在动手之前我们必须先理解为什么EasyExcel不直接提供这个功能以及有哪些可能的实现路径。2.1 EasyExcel的默认行为与局限EasyExcel的设计哲学是简单、高效特别是在处理大数据量时它默认使用SXSSFWorkbook流式导出只将部分数据保留在内存中。这种模式下它在写入数据时无法预知整列或整行的最终内容长度。如果它在每写一行时就尝试计算整张表的最佳尺寸会严重破坏流式导出的性能优势甚至可能导致内存溢出。因此它把单元格样式的控制权包括宽度和高度更多地交给了开发者通过ExcelProperty注解或WriteCellStyle来预设。2.2 可行的技术方案对比要实现自适应我们必须在数据写入完成后对已经生成的Sheet进行“后处理”。主要有三种思路遍历计算法推荐在所有数据写入完成后遍历Sheet中的每一行、每一列计算每个单元格内容的像素宽度/高度然后取该列最宽值、该行最高值设置为列宽和行高。这是最精准、最通用的方法。估算法根据中文字符、英文字符、数字的大致比例估算一个固定系数。例如设定一个基础列宽然后根据字段内容的预估最大长度如数据库字段varchar(255)来设置。这种方法简单但不精确容易留白或溢出。模板预设法先创建一个“标准”Excel模板在模板中手动调整好列宽行高导出时加载这个模板。这适用于列结构固定的报表但失去了动态性。为什么我们选择“遍历计算法”因为它能真正响应数据内容实现真正的“自适应”。无论内容是用户填写的长文本还是从数据库查出的不定长字符串都能得到最合适的展示。虽然它需要额外的遍历计算开销但对于一次性的导出操作这点开销在用户体验提升面前是完全可以接受的。接下来我们就基于这个方案进行深度拆解。3. 核心工具类自适应宽度与高度计算器这是整个功能的核心。我们将创建一个工具类它接收一个SXSSFSheet对象然后对其执行自适应调整。3.1 自适应列宽计算器实现列宽的计算是难点。Excel的列宽单位不是像素也不是字符数而是一个特殊的单位。POI提供了Sheet的setColumnWidth(int columnIndex, int width)方法其中的width是1/256个字符宽度。我们需要把字符串的像素宽度转换成这个单位。一个关键点是不同字体、不同字号下字符宽度不同。我们需要获取工作簿中使用的字体来精确计算。以下是核心代码实现与详解import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import java.awt.*; import java.util.HashMap; import java.util.Map; public class ExcelAutoSizeUtil { /** * 自动设置列宽核心方法 * param sheet 需要调整的Sheet * param extraWidth 额外增加的宽度单位字符数用于给单元格一些边距避免显得拥挤。 */ public static void autoSizeColumns(SXSSFSheet sheet, float extraWidth) { if (sheet null) return; SXSSFWorkbook workbook sheet.getWorkbook(); // 用于缓存每列计算出的最大宽度避免重复计算 MapInteger, Integer maxWidthMap new HashMap(); // 获取默认字体用于计算字符串宽度 Font defaultFont workbook.getFontAt((short) 0); FontMetrics fontMetrics getFontMetrics(defaultFont); // 遍历所有有数据的行 for (Row row : sheet) { if (row null) continue; for (Cell cell : row) { if (cell null) continue; int columnIndex cell.getColumnIndex(); // 获取单元格内容字符串 String cellValue getCellValueAsString(cell, workbook); if (cellValue null || cellValue.isEmpty()) { continue; } // 计算字符串的像素宽度 int stringWidthPx fontMetrics.stringWidth(cellValue); // 关键转换将像素宽度转换为Excel的列宽单位1/256字符宽 // 这里是一个经验公式像素宽度 / 单个字符平均像素宽度 * 256 额外宽度补偿 // 单个字符平均像素宽度可以通过测量“汉”或“W”等宽字符获得这里简化处理 int cellWidthUnits (int) ((stringWidthPx / (fontMetrics.charWidth(W) * 1.0)) * 256); cellWidthUnits (int)(extraWidth * 256); // 加上额外边距 // 更新该列的最大宽度 int currentMaxWidth maxWidthMap.getOrDefault(columnIndex, 0); if (cellWidthUnits currentMaxWidth) { // Excel列宽有上限255*256需要防止溢出 maxWidthMap.put(columnIndex, Math.min(cellWidthUnits, 255 * 256)); } } } // 遍历缓存将计算出的最大宽度应用到Sheet的每一列 for (Map.EntryInteger, Integer entry : maxWidthMap.entrySet()) { int columnIndex entry.getKey(); int columnWidth entry.getValue(); // 设置一个最小宽度避免内容太短时列宽为0 columnWidth Math.max(columnWidth, 8 * 256); // 最小8个字符宽 sheet.setColumnWidth(columnIndex, columnWidth); } } /** * 获取AWT环境下的FontMetrics用于精确计算字符串像素宽度。 * 注意在无头环境Headless如Linux服务器下需要特殊处理。 */ private static FontMetrics getFontMetrics(Font font) { // 这是一个简化示例。在生产环境中你需要处理Headless环境。 // 通常可以创建一个虚拟的BufferedImage来获取Graphics2D和FontMetrics。 Canvas canvas new Canvas(); return canvas.getFontMetrics(font); } /** * 将Cell内容转换为字符串处理不同类型数字、日期、公式等 */ private static String getCellValueAsString(Cell cell, Workbook workbook) { if (cell null) return ; switch (cell.getCellType()) { case STRING: return cell.getStringCellValue(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { // 日期格式按原格式字符串化或使用默认格式 DataFormatter formatter new DataFormatter(); return formatter.formatCellValue(cell); } // 数字类型防止科学计数法保留适当精度 return String.valueOf(cell.getNumericCellValue()); case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); case FORMULA: // 公式单元格尝试计算值如果计算失败则返回公式字符串 try { return String.valueOf(cell.getNumericCellValue()); } catch (Exception e) { return cell.getCellFormula(); } case BLANK: return ; default: return ; } } }重要提示踩坑经验getFontMetrics方法在服务器尤其是无图形界面的Linux服务器上运行时可能会抛出HeadlessException。这是实现自适应列宽最常见的坑。解决方案是要么在服务器环境中安装图形库不推荐要么放弃精确像素计算采用一种更通用的“字符数估算”方法。我们接下来会提供一个更健壮的、无头环境友好的版本。3.2 无头环境友好的列宽估算方案既然精确计算像素在服务器端有障碍我们可以采用一个业界常用的经验法则一个中文汉字大致相当于2个英文字符的宽度。我们可以通过计算单元格字符串的“有效字符数”来估算列宽。/** * 自动设置列宽无头环境/服务器端友好版 * param sheet 需要调整的Sheet * param extraWidth 额外宽度字符数 */ public static void autoSizeColumnsServerFriendly(SXSSFSheet sheet, float extraWidth) { if (sheet null) return; MapInteger, Integer maxWidthMap new HashMap(); for (Row row : sheet) { if (row null) continue; for (Cell cell : row) { if (cell null) continue; int columnIndex cell.getColumnIndex(); String cellValue getCellValueAsString(cell, sheet.getWorkbook()); if (cellValue null || cellValue.isEmpty()) continue; // 核心估算逻辑计算字符串的“显示长度” int displayLength calculateDisplayLength(cellValue); // 转换为Excel列宽单位。经验值一个显示长度单位约等于256个列宽单位。 // 额外宽度也按比例加上。 int cellWidthUnits (displayLength * 256) (int)(extraWidth * 256); int currentMax maxWidthMap.getOrDefault(columnIndex, 0); if (cellWidthUnits currentMax) { // 同样设置上限 maxWidthMap.put(columnIndex, Math.min(cellWidthUnits, 255 * 256)); } } } for (Map.EntryInteger, Integer entry : maxWidthMap.entrySet()) { int columnIndex entry.getKey(); int columnWidth entry.getValue(); columnWidth Math.max(columnWidth, 10 * 256); // 设置一个更合理的最小宽度 sheet.setColumnWidth(columnIndex, columnWidth); } } /** * 计算字符串在Excel中的大致显示长度。 * 规则ASCII字符英文字母、数字、标点计为0.5中文及其他全角字符计为1。 * 这是一个简化模型但实践中效果很好。 */ private static int calculateDisplayLength(String str) { if (str null) return 0; int length 0; for (int i 0; i str.length(); i) { char c str.charAt(i); // 判断是否为ASCII字符范围0-127 if (c 127) { length 1; // ASCII字符占位较窄这里计为1个单位后续会整体调整比例 } else { length 2; // 非ASCII字符如中文占位较宽计为2个单位 } } // 因为我们将一个ASCII字符的宽度单位视为1但实际列宽单位更细 // 所以返回总长度即可系数在调用处统一乘。 return length; }这个方案牺牲了一点精确性但获得了极强的环境兼容性是生产环境的首选。3.3 自适应行高计算器实现行高的实现比列宽简单因为Excel的行高单位是“点”point与字体大小直接相关。我们通常希望行高能根据单元格内文本的行数是否换行来自适应。/** * 自动设置行高 * param sheet 需要调整的Sheet * param extraHeight 额外增加的高度点数使文字不顶格。 */ public static void autoSizeRows(SXSSFSheet sheet, float extraHeight) { if (sheet null) return; // 获取默认行高点数 float defaultRowHeightInPoints sheet.getDefaultRowHeightInPoints(); for (Row row : sheet) { if (row null) continue; // 初始化为默认行高 float maxHeightInPoints defaultRowHeightInPoints; // 遍历该行所有单元格找出需要最高行高的那个 for (Cell cell : row) { if (cell null) continue; CellStyle cellStyle cell.getCellStyle(); // 获取该单元格使用的字体 Font font sheet.getWorkbook().getFontAt(cellStyle.getFontIndex()); String cellValue getCellValueAsString(cell, sheet.getWorkbook()); if (cellValue null || cellValue.isEmpty()) continue; // 关键判断单元格内容是否换行。 // 1. 单元格样式设置了自动换行wrapText // 2. 字符串本身包含换行符“\n” boolean wrapText cellStyle.getWrapText(); boolean hasNewLine cellValue.contains(\n); if (wrapText || hasNewLine) { // 对于换行文本需要估算行数。 // 这里我们做一个简化估算每行大约能显示多少个字符。 // 首先需要知道列宽字符数。这有点循环依赖因为列宽可能刚被我们调整过。 // 一个更简单实用的策略根据字体大小和内容长度设置一个固定的多行行高。 // 例如每增加一个换行符或每超过一定字符长度就增加一行的高度。 int estimatedLines estimateLinesOfText(cellValue, font, sheet.getColumnWidth(cell.getColumnIndex())); // 每行行高大致等于字体大小点数加上一些间距 float lineHeight font.getFontHeightInPoints() 2; float neededHeight estimatedLines * lineHeight; if (neededHeight maxHeightInPoints) { maxHeightInPoints neededHeight; } } else { // 对于不换行的单行文本行高至少等于字体高度 float fontHeight font.getFontHeightInPoints(); if (fontHeight maxHeightInPoints) { maxHeightInPoints fontHeight; } } } // 应用计算出的行高并加上额外边距 row.setHeightInPoints(maxHeightInPoints extraHeight); } } /** * 估算文本在给定列宽和字体下的行数简化版 * 注意这是一个非常粗略的估算精确计算需要复杂的字体度量。 * 生产环境可根据实际情况调整算法或接受一定误差。 */ private static int estimateLinesOfText(String text, Font font, int columnWidthInUnits) { // 将列宽单位转换为大概的字符数。 // 假设一个标准英文字符宽度为256单位则列宽字符数 ≈ columnWidthInUnits / 256 double approxCharWidth 256.0; int charsPerLine (int) (columnWidthInUnits / approxCharWidth); if (charsPerLine 0) charsPerLine 50; // 给个默认值 // 计算文本总字符数按之前定义的显示长度 int totalDisplayLength calculateDisplayLength(text); // 计算基础行数 int lines (int) Math.ceil(totalDisplayLength * 1.0 / charsPerLine); // 如果文本本身有换行符行数要加上换行符的数量 long newLineCount text.chars().filter(ch - ch \n).count(); lines newLineCount; return Math.max(lines, 1); // 至少一行 }实操心得行高的自适应比列宽更“模糊”因为文本换行不仅取决于列宽还取决于具体的字体和渲染引擎。上述estimateLinesOfText方法是一个启发式算法在大多数情况下能提供可接受的结果。如果对行高精度要求极高如生成用于打印的精密报表可能需要考虑更复杂的方案或者引导用户使用Excel自身的“自动调整行高”功能但这需要手动触发。4. 与EasyExcel集成在写入完成后触发自适应有了核心工具类我们需要将其集成到EasyExcel的写入流程中。EasyExcel提供了SheetWriteHandler这个拦截器接口允许我们在Sheet写入完成后执行自定义操作。这是最理想的集成点。4.1 实现自定义SheetWriteHandlerimport com.alibaba.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext; import lombok.extern.slf4j.Slf4j; import org.apache.poi.xssf.streaming.SXSSFSheet; Slf4j public class AutoSizeSheetWriteHandler implements SheetWriteHandler { // 额外宽度字符数 private float extraColumnWidth; // 额外高度点数 private float extraRowHeight; public AutoSizeSheetWriteHandler() { this(1.5f, 4.0f); // 提供默认值 } public AutoSizeSheetWriteHandler(float extraColumnWidth, float extraRowHeight) { this.extraColumnWidth extraColumnWidth; this.extraRowHeight extraRowHeight; } Override public void afterSheetCreate(SheetWriteHandlerContext context) { // 这个阶段Sheet已创建但数据未写入不处理 } Override public void afterSheetDispose(SheetWriteHandlerContext context) { // 关键在所有数据写入Sheet之后文件写出之前执行自适应调整 log.info(开始执行Excel自适应列宽行高调整...); try { // 获取POI原生的SXSSFSheet对象 SXSSFSheet sheet (SXSSFSheet) context.getWriteSheetHolder().getSheet(); // 1. 先调整列宽因为行高估算可能依赖列宽 ExcelAutoSizeUtil.autoSizeColumnsServerFriendly(sheet, extraColumnWidth); // 2. 再调整行高 ExcelAutoSizeUtil.autoSizeRows(sheet, extraRowHeight); log.info(Excel自适应调整完成。); } catch (Exception e) { log.error(Excel自适应调整失败将导出默认格式文件。, e); // 此处不要抛出异常以免影响主流程。记录日志后导出未调整的文件。 } } }4.2 在EasyExcel写入器中注册处理器现在在调用EasyExcel进行写入时只需将这个SheetWriteHandler注册进去即可。// 准备数据 ListYourDataModel dataList ...; // 设置导出文件路径 String fileName 自适应报表.xlsx; // 使用EasyExcel的写入器并注册我们的自适应处理器 EasyExcel.write(fileName, YourDataModel.class) .registerWriteHandler(new AutoSizeSheetWriteHandler(2.0f, 6.0f)) // 可以自定义边距 // 可以注册其他处理器如样式处理器 // .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // EasyExcel自带的简单列宽策略但不够智能建议不用或作为备选 .sheet(数据Sheet) .doWrite(dataList);关键点解释extraColumnWidth我设置为2.0f意味着在计算出的最合适列宽基础上再增加大约2个字符的宽度这样单元格内容不会紧贴边框视觉效果更舒适。extraRowHeight设置为6.0f点在字体高度基础上增加6点约2毫米的行高确保文字上下有足够的留白。执行顺序一定要先调整列宽再调整行高。因为行高的估算对于换行文本依赖于最终的列宽值。5. 高级优化与生产级注意事项上面的代码已经可以解决大部分问题但在生产环境中我们还需要考虑更多细节。5.1 性能优化大数据量下的处理策略当导出数据行数上万时遍历所有单元格计算宽度可能会比较耗时。我们可以进行以下优化抽样计算对于数据量极大的列如万行以上可以不必遍历每一行。可以随机抽取一定比例如前1000行中间1000行最后1000行的样本进行计算以样本中的最大宽度作为列宽。这牺牲了极小概率的精度换来了显著的性能提升。缓存字体度量FontMetrics的创建和获取有一定开销。如果使用精确计算法应该在整个工作簿级别缓存字体及其对应的FontMetrics对象。异步或分批处理对于超大数据量如数十万行自适应调整本身可能成为瓶颈。可以考虑将“写出文件”和“自适应调整”分离。先快速写出一个未调整的临时文件然后通过一个后台任务或第二步处理来读取这个文件并进行自适应调整最后提供下载。5.2 样式兼容性处理我们的自适应处理器可能会和其他样式处理器比如设置单元格背景色、边框、字体加粗等产生冲突。需要注意执行顺序EasyExcel的WriteHandler执行有顺序。通常样式处理器CellWriteHandler在SheetWriteHandler之前执行。这意味着我们的自适应调整是最后一步会覆盖之前设置的固定列宽行高。这通常是我们期望的。合并单元格如果导出的表格有合并单元格自适应逻辑会变得复杂。因为一个合并区域占据多列多行。我们的遍历计算法在遇到合并单元格时需要特殊处理通常只需考虑合并区域左上角那个单元格的内容但宽度要应用到合并的所有列上。POI提供了Sheet.getMergedRegions()方法来获取所有合并区域在计算列宽时需要跳过被合并的内部单元格。5.3 处理特殊内容与格式超长文本如果一个单元格包含极长的文本如一段描述即使我们设置了自动换行计算出的行高可能会非常高导致一行占据大半页。可以设置一个行高上限例如100点超过此高度则强制截断文本或保持默认行高并添加注释。公式单元格公式单元格的值在写入时可能是公式字符串如A1B1而不是计算结果。我们的getCellValueAsString方法尝试获取计算值但如果公式依赖其他未计算的值可能会失败。对于公式单元格自适应其宽度可能比较困难一个稳妥的做法是给它一个预设的较宽宽度或者忽略其自适应。图片或其他对象如果单元格中插入了图片POI的Cell类型是CELL_TYPE_BLANK我们的方法会跳过。图片的尺寸需要单独处理这超出了本文范围通常需要根据图片大小来设置行高列宽。6. 常见问题排查与实战技巧在实际使用中你可能会遇到下面这些问题这里是我的排查思路和解决方案。6.1 问题速查表问题现象可能原因解决方案导出的文件列宽仍然很窄内容显示为###1. 自适应处理器未生效。2. 计算出的列宽单位超过Excel上限65535。3. 中文字符宽度计算严重偏差。1. 检查SheetWriteHandler是否被正确注册到EasyExcel.write()链中。2. 在autoSizeColumns方法中确保对columnWidth进行了上限检查Math.min(cellWidthUnits, 255 * 256)。3. 切换到“无头环境友好版”的估算方法并调整calculateDisplayLength中的宽度系数如将中文计为2英文计为1。服务器Linux上运行报HeadlessException使用了依赖AWT图形环境的精确像素计算。务必使用autoSizeColumnsServerFriendly方法它不依赖FontMetrics。行高调整后多行文本仍然显示不全行高估算函数estimateLinesOfText不准确低估了行数。增大charsPerLine的估算值即认为每行能显示的字符数更少或直接增加extraRowHeight的值。更激进的方法是用(字体大小 * 行数 * 一个放大系数如1.5)来设置行高。自适应后表头第一行的列宽不合适表头文字通常比数据内容短遍历计算时可能以数据列为准导致表头看起来空旷。在遍历计算列宽时强制将表头行纳入计算。可以在工具类中加一个参数boolean includeHeader或者在遍历前先单独处理表头行确保其宽度也被考虑在内。性能慢导出大量数据时卡顿遍历了所有单元格且计算复杂。实施5.1节的性能优化策略特别是抽样计算。对于超过1万行的数据只计算前、中、后各5%的行效果和全量计算相差无几但速度提升巨大。设置了固定列宽的列也被自适应调整了自适应处理器覆盖了之前通过ColumnWidth注解或样式设置的固定宽度。如果希望某些列保持固定宽度可以在自适应逻辑中增加“排除列”列表。在计算列宽前检查列索引是否在排除列表中如果是则跳过该列的计算。6.2 我的独家调参心得extraColumnWidth额外列宽这个参数非常关键。我经过大量测试发现1.5到3.0是一个黄金区间。小于1.5单元格看起来会有点挤大于3.0表格又会显得松散。对于以中文为主的报表建议从2.0开始尝试。extraRowHeight额外行高这个参数影响阅读的舒适度。默认字体11号下4.0到6.0点比较合适。如果你使用了更大的表头字体如14号加粗可能需要为表头行单独设置更大的额外行高。最小列宽一定要设置一个合理的最小列宽如8 * 256或10 * 256防止内容极短的列如“性别”、“是/否”变得几乎看不见。先列宽后行高这个顺序不能错。我早期版本曾反过来导致换行文本的行高估算严重错误因为当时依据的是调整前的、过窄的列宽。6.3 一个更健壮的生产级工具类片段结合以上所有经验这里给出一个增强版的列宽计算入口方法它集成了抽样、排除列、最小最大宽度限制等功能。/** * 增强版自动列宽调整生产环境推荐 * param sheet 工作表 * param extraWidth 额外宽度 * param excludedColumns 需要排除自适应、保持原样的列索引集合 * param sampleRate 抽样率0.1表示抽样10%的行进行计算。0 或 1 表示全量计算。 */ public static void autoSizeColumnsEnhanced(SXSSFSheet sheet, float extraWidth, SetInteger excludedColumns, double sampleRate) { // ... 参数校验 MapInteger, Integer maxWidthMap new HashMap(); int lastRowNum sheet.getLastRowNum(); if (lastRowNum 0) return; // 决定需要计算的行 ListInteger rowsToProcess new ArrayList(); if (sampleRate 0 sampleRate 1) { // 抽样逻辑取首、中、尾各一部分行 int sampleSize (int) (lastRowNum * sampleRate); int start Math.max(0, sampleSize); int middle lastRowNum / 2; int end Math.max(lastRowNum - sampleSize, middle 1); // 添加一些固定行如第0行表头 rowsToProcess.add(0); for (int i start; i start sampleSize i lastRowNum; i) rowsToProcess.add(i); for (int i middle; i middle sampleSize/2 i lastRowNum; i) rowsToProcess.add(i); for (int i end; i lastRowNum; i) rowsToProcess.add(i); } else { // 全量处理 for (int i 0; i lastRowNum; i) rowsToProcess.add(i); } // 遍历选中的行进行计算 for (int rowIndex : rowsToProcess) { Row row sheet.getRow(rowIndex); if (row null) continue; // ... 遍历单元格计算逻辑跳过excludedColumns中的列 ... } // 应用宽度并确保在最小和最大范围内 int minWidth 10 * 256; // 最小10字符 int maxWidth 100 * 256; // 最大100字符 for (Map.EntryInteger, Integer entry : maxWidthMap.entrySet()) { int colIdx entry.getKey(); if (excludedColumns ! null excludedColumns.contains(colIdx)) { continue; // 跳过排除列 } int width Math.max(minWidth, Math.min(maxWidth, entry.getValue())); sheet.setColumnWidth(colIdx, width); } }这个增强版工具提供了更大的灵活性和可控性能够应对更复杂的生产场景。
返回列表