JXL 2.6.12 解析 .xls 实战:对象模型、导入导出实现与踩坑指南 📅 发布时间:2026/9/14 12:06:09 👁 浏览次数: 简介提供一套基于Java Excel API (JXL) 实现的Excel导入导出工具类源码包面向需要在Java项目中快速处理.xls格式数据的开发者尤其适合中小规模数据交互场景。压缩包共9个文件大小约1.47MB包含4个Jar依赖、3个Java源文件、1份README说明与1份License授权文件。其中Jar包覆盖JXL核心库及commons-collections、commons-lang、servlet-api等辅助依赖源码则封装了导入导出常用方法便于直接集成或二次改造。已有111人学习下载。通过学习可掌握JXL读取工作簿、遍历工作表行列单元格、创建Workbook、写入带格式数据等关键操作还能了解WritableFont与WritableCellFormat实现字体、颜色、对齐、边框等样式设置以及针对大文件分批写入和异常处理的优化思路。资源附有读取说明与许可协议帮助开发者在合规前提下快速上手提升Excel数据交互开发效率。1. JXL 2.6.12 处理 .xls 的边界与选型逻辑JXLJava Excel API是 2000 年代末活跃的开源纯 Java 类库项目里那份 jxl-2.6.12.jar 一旦出现基本就锁定了目标文件格式Excel 97-2003 的 .xls。换句话说这套基于 JXL 的导入导出工具类能稳定处理老式二进制工作簿但不碰 .xlsx。适合那些还在对接遗留 ERP、财务系统导出表格、批量上报数据的 Java 项目如果你已经全面切换 OOXML需要的是 POI 或 EasyExcel而不是 JXL。本文围绕这个工具类实际承载的四个 jarjxl、commons-lang、commons-collections、javax.servlet-api拆开讲工作簿对象模型、导入导出两个核心管线的实现方式以及 2.6.12 常见的编码、内存、类型兼容坑。2. JXL 工作簿对象模型与工具类依赖的职责划分2.1 四个依赖 jar 在工程里分别承担什么打开 zip 里的 lib 目录你会看到jar版本工具类中实际用途jxl-2.6.12.jar2.6.12读写 .xls 的核心 APIWorkbook、Sheet、Cell 全在这commons-lang-2.6.jar2.6StringUtils 做字符串判空、拼接、trimcommons-collections-3.2.1.jar3.2.1CollectionUtils 判断集合空、批量转换javax.servlet-api-3.1.0.jar3.1.0导出时通过 HttpServletResponse 把工作簿推给浏览器这是一次典型的瘦封装真正的 Excel 操作全部交给 JXL两个 commons 组件负责日常工具性代码Servlet API 只出现在 Web 导出的那一个方法上不参与文件读写逻辑。我一般会把这种工具类放进com.xxx.util.excel包类名直接叫ExcelUtils对外暴露两个静态方法importExcel(File)和exportExcel(List?)内部实现按导入、导出两条支线拆开。选择 JXL 而不是 POI在当时是内存和 API 复杂度的考量。JXL 读取工作簿是一次性加载到内存整体模型简单没有 POI 的 WorkbookFactory 那一堆分支缺点是它不支持 xlsx样式能力也比 HSSF 弱一截。也就是说这个工具类的适用坐标系是“表格结构规整、格式要求不高、量级在万行以下”。2.2 三级对象模型Workbook、Sheet、CellJXL 的读写模型分两套接口。只读侧是Workbook、Sheet、Cell位于jxl包写入侧是WritableWorkbook、WritableSheet、WritableCell位于jxl.write包。写操作从Workbook.createWorkbook(File)得到WritableWorkbook再往里添加WritableSheet。// 读取侧拿到第一个工作表行列遍历 Workbook workbook Workbook.getWorkbook(new File(sample.xls)); try { Sheet sheet workbook.getSheet(0); int rows sheet.getRows(); // 总行数从1开始 int cols sheet.getColumns(); // 总列数 for (int row 0; row rows; row) { StringBuilder line new StringBuilder(); for (int col 0; col cols; col) { Cell cell sheet.getCell(col, row); line.append(cell.getContents()).append( | ); } System.out.println(line); } } finally { workbook.close(); // 必须关闭否则文件句柄泄漏 }这里sheet.getCell(col, row)参数顺序是列在前、行在后新手第一次写容易调反结果就是拿到同一行不同列的数据。getContents()是所有 Cell 子类都有的方法统一返回字符串适合预览如果要做类型转换需要走CellType分支见下一章。关于workbook.close()JXL 读取时会把整个 .xls 文件映入手持的随机访问结构里不关 close 会造成文件被占用和内存迟迟不释放。同一文件在一次导入流程中只能被读取一次重复读取需要重新Workbook.getWorkbook。这是我实际拆过的老项目里最常见的问题——把 Workbook 当成了像 JDBC Connection 那样的可复用资源。3. 导入 Excel 的静态工具方法设计与类型兜底3.1 用 WorkbookSettings 控制读取编码和容错导入的第一步不是直接Workbook.getWorkbook(file)而是先配置WorkbookSettings。多数乱码和解析失败都源于默认编码和文件实际编码不一致。WorkbookSettings settings new WorkbookSettings(); settings.setEncoding(UTF-8); // 默认是 ISO-8859-1中文容易乱码 settings.setGCDisabled(true); // 关闭垃圾回收调用避免频繁 GC settings.setSuppressWarnings(true); // 抑制版本警告信息 Workbook workbook Workbook.getWorkbook(file, settings);setEncoding影响的是 JXL 读取字符串单元格时使用的字符集。如果你的 .xls 由老式 Windows 系统导出可能实际是 GBK这种情况下应该改成GBK。setGCDisabled(true)是性能选项JXL 默认会在解析过程中间歇调用System.gc()来管理内存在高并发场景下反而拖慢整个 JVM关掉改为依赖 JVM 自身的 GC 策略更稳。setSuppressWarnings只是压制输出告警设置后日志干净。3.2 按 CellType 分支而不是 getContents 一把梭getContents()虽然省事但它把所有单元格都渲染成字符串问题在于数字精度和日期格式会被 Excel 显示格式干扰。比如单元格存的是 12.345但 Excel 显示两位小数getContents()可能返回12.35精度就丢了。正确处理是拿到Cell后先判断类型再做类型强转。Object value null; Cell cell sheet.getCell(col, row); CellType type cell.getType(); if (type CellType.NUMBER) { NumberCell nc (NumberCell) cell; value nc.getValue(); // 返回 double保留完整精度 } else if (type CellType.DATE) { DateCell dc (DateCell) cell; value dc.getDate(); // 返回 java.util.Date } else if (type CellType.BOOLEAN) { BooleanCell bc (BooleanCell) cell; value bc.getValue(); // 返回 boolean } else if (type CellType.LABEL) { LabelCell lc (LabelCell) cell; value lc.getString(); } else { value cell.getContents(); // FORMULA / BLANK 等兜底 }JXL 的DateCell有点特别单元格靠日期格式或特殊类型来标识如果是数字但设置了日期显示格式getType()返回的仍是DATE。FormulaCell拿到的是公式字符串本身而不是计算结果要拿计算结果得在 Excel 里以值方式存储或者读取时用WorkbookSettings.setFormulaCellValue之类的扩展——2.6.12 里支持有限最好在写入端就避免用公式。3.3 把行数据封装进业务对象工具类最后一步是把每行原始值翻译成业务对象。这里要处理列顺序和字段名的映射。常见做法是用一个表头映射数组第一行作为表头索引。public static ListMapString, Object importRows(File file, String[] headers) { ListMapString, Object result new ArrayList(); Workbook workbook null; try { WorkbookSettings settings new WorkbookSettings(); settings.setEncoding(UTF-8); workbook Workbook.getWorkbook(file, settings); Sheet sheet workbook.getSheet(0); int rowStart headers ! null headers.length 0 ? 1 : 0; for (int row rowStart; row sheet.getRows(); row) { MapString, Object rowMap new HashMap(); for (int col 0; col headers.length; col) { rowMap.put(headers[col], getCellValue(sheet, col, row)); } result.add(rowMap); } } catch (Exception e) { throw new RuntimeException(导入 Excel 失败文件格式可能不是 .xls, e); } finally { if (workbook ! null) { workbook.close(); } } return result; }参数headers的作用是同时承担“表头行校验”和“列名映射”两个任务数组元素顺序对应当前列序值对应用户的字段名。如果首行不是表头传null就会从第 0 行开始读map 的 key 变成col_0、col_1这种占位符。这里把类型转换集中在getCellValue里如果单元格是数字map 里放Double是日期放Date是字符串放String。调用方再根据业务字段类型做二次转换避免在工具类里硬编码 DTO 类型保持了通用性。这个阶段最常见的坑是空行。Excel 里“看起来空”的行可能残留格式sheet.getRows()会包含它们读出全是空值的 map。建议在循环里判断如果该行所有单元格都是BLANK或者是空字符串直接 continue 掉。4. 导出 Excel 的写样式与批量写入策略4.1 导出单元格格式的三个基本对象写入侧的能力远比读取侧复杂核心对象是WritableFont、WritableCellFormat和WritableCell。前者定义字体、字号、粗体次者把字体和对齐、边框、背景色组合起来最后实例化单元格时应用。WritableFont headerFont new WritableFont(WritableFont.createFont(微软雅黑), 11, WritableFont.BOLD); WritableCellFormat headerFormat new WritableCellFormat(headerFont); headerFormat.setAlignment(Alignment.CENTRE); headerFormat.setBackground(Colour.GRAY_25); WritableWorkbook workbook Workbook.createWorkbook(outputFile); WritableSheet sheet workbook.createSheet(报表, 0); sheet.addCell(new Label(0, 0, 订单号, headerFormat)); sheet.addCell(new Label(1, 0, 金额, headerFormat));createFont传入的字体名必须和运行环境匹配Linux 服务器没有微软雅黑时会回退到默认字体。Label用于字符串Number用于数值DateTime用于日期Boolean用于布尔这几个类都实现了WritableCell。这里的列号、行号同样从 0 开始。4.2 数字格式和日期格式的显式声明导出数值类单元格时最容易出现的问题是 double 类型被 JXL 默认格式写出出现一堆小数位。指定格式的方式是给单元格设置数字格式对象。NumberFormat nf new NumberFormat(#,##0.00); WritableCellFormat numberFormat new WritableCellFormat(nf); numberFormat.setAlignment(Alignment.RIGHT); Number amountCell new Number(1, rowIdx, 12345.678, numberFormat); sheet.addCell(amountCell); DateFormat df new DateFormat(yyyy-MM-dd HH:mm:ss); WritableCellFormat dateFormat new WritableCellFormat(df); DateTime dateCell new DateTime(2, rowIdx, new Date(), dateFormat); sheet.addCell(dateCell);NumberFormat和DateFormat来自jxl.write包它们不会像String.format那样先把数值变成字符串再写入而是以原始值 格式模板的形式存进 .xls。这样既能控制显示效果又能保证 Excel 里单元格仍是数值类型后续在 Excel 端做 SUMIFS 这类函数公式计算时不会因为文本类型而报错。4.3 数据量上来时的分批写入和 flushWritableWorkbook在write()时才真正落盘所以大批量导出时所有单元格会积累在内存里。JXL 文档里提到的“分批写入”实际指的是每写满一批后先保证引用不释放适时进行workbook.write()与workbook.close()的配合而不是提供一个公开的增量保存 API。public static void exportLargeData(ListOrderVO orders, OutputStream out) throws IOException { WritableWorkbook workbook Workbook.createWorkbook(out); WritableSheet sheet workbook.createSheet(订单, 0); for (int i 0; i orders.size(); i) { OrderVO order orders.get(i); int row i 1; sheet.addCell(new Label(0, row, order.getOrderNo())); sheet.addCell(new Number(1, row, order.getAmount())); if (row % 5000 0) { System.out.println(已写入 row 行行号继续递增); } } workbook.write(); workbook.close(); }这里有个常见的性能误读workbook.write()只能调用一次且在close()之前调用每 5000 行做一次“说明”并不会触发增量落盘。真正控制内存的是OutputStream使用方式——传给Workbook.createWorkbook(OutputStream)比传File更可控配合BufferedOutputStream能减少单次 IO 开销。另外 JXL 对单个Label字符串长度有限制超过 32767 个字符会抛异常导出长文本时先截断或改用备注单元格。大数量导出时行数是 int 类型sheet.getRows()和addCell的行号上限在 2.6.12 里是65536行这是 .xls 格式本身的硬限制超了就写不进去。这一点在项目里必须提前预判否则线上导出到一半才报错用户那边的体验会很差。5. 内存占用、编码乱码与 .xlsx 迁移的验证手段5.1 内存溢出时的排查路径JXL 在读取和写入时都会把整个工作簿结构放进 JVM 堆内存常见的内存问题集中在导入大文件场景。压测时如果出现java.lang.OutOfMemoryError: Java heap space优先检查三件事是否在循环里反复创建 WorkbookWorkbookSettings 是否设置了setGCDisabled(true)导出的数据模型是否在业务层提前加载了过量关联数据。JVM 参数-Xmx调高是最后手段不是第一手段。第一点比较隐蔽。有的项目会在 service 层做“多个 sheet 分别导入”每个 sheet 都单独调一次Workbook.getWorkbook导致同一文件被映射多次。正确用法是一次读取、多次getSheet。5.2 乱码的三类原因定位中文乱码在 JXL 场景下分三种形态表现和修法完全不同。乱码形态原因验证方法导入后 Java 侧 getString 乱码读取编码与文件实际编码不符用 Hex 查看文件头或换 GBK/UTF-8 逐一尝试导出后 Excel 打开正常、Excel 复制到网页乱码Servlet 响应头缺 Content-Type设置application/vnd.ms-excel和charset老系统导出的文件某些中文单元格变问号原文件本身是 GBK 且 JXL 标称编码不支持用旧版系统重存或转换编码针对第二点常见的 Servlet 导出写法是response.setContentType(application/vnd.ms-excel;charsetUTF-8); response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(导出, UTF-8) .xls);5.3 xlsx 迁移的两条路径jxl 2.6.12 完全不识别 xlsx读它时抛异常。迁移路径一般两条一条是引入 POI 的 HSSFWorkbook 替换读取 .xls 的部分因为 JXL 和 HSSF 对象模型高度接近另一条是用WorkbookFactory自动判断格式但前提是原来用 JXL 的代码不能原样搬。实际上对维护过 2.6.12 工具类的团队我建议保留 JXL 路径处理历史 .xls 文件同时新增一条 EasyExcel 路径处理新产生的 xlsx两条路径先用一个抽象接口隔开而不是立刻重写全部代码。这样上线后即使新路径出问题回滚手段也是现成的。工具类里补充一个自动识别扩展名的方法落到前面是最省事的方式扩展名是.xls走 JXL是.xlsx走新接口兜底的异常信息提示用户“文件格式不支持”。这类兼容方法建议直接放在工具类内方便全项目复用。本文还有配套的精品资源点击获取