JXL 2.6.12 解析 .xls 实战:对象模型、导入导出实现与踩坑指南
2026/9/14 12:06:07 网站建设 项目流程

简介:提供一套基于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 的边界与选型逻辑

JXL(Java Excel API)是 2000 年代末活跃的开源纯 Java 类库,项目里那份 jxl-2.6.12.jar 一旦出现,基本就锁定了目标文件格式:Excel 97-2003 的 .xls。换句话说,这套基于 JXL 的导入导出工具类能稳定处理老式二进制工作簿,但不碰 .xlsx。适合那些还在对接遗留 ERP、财务系统导出表格、批量上报数据的 Java 项目;如果你已经全面切换 OOXML,需要的是 POI 或 EasyExcel,而不是 JXL。本文围绕这个工具类实际承载的四个 jar(jxl、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 的核心 API,Workbook、Sheet、Cell 全在这
commons-lang-2.6.jar2.6StringUtils 做字符串判空、拼接、trim
commons-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、Cell

JXL 的读写模型分两套接口。只读侧是WorkbookSheetCell,位于jxl包;写入侧是WritableWorkbookWritableSheetWritableCell,位于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()返回的仍是DATEFormulaCell拿到的是公式字符串本身,而不是计算结果;要拿计算结果,得在 Excel 里以值方式存储,或者读取时用WorkbookSettings.setFormulaCellValue之类的扩展——2.6.12 里支持有限,最好在写入端就避免用公式。

3.3 把行数据封装进业务对象

工具类最后一步是把每行原始值翻译成业务对象。这里要处理列顺序和字段名的映射。常见做法是用一个表头映射数组,第一行作为表头索引。

public static List<Map<String, Object>> importRows(File file, String[] headers) { List<Map<String, 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++) { Map<String, 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 导出单元格格式的三个基本对象

写入侧的能力远比读取侧复杂,核心对象是WritableFontWritableCellFormatWritableCell。前者定义字体、字号、粗体,次者把字体和对齐、边框、背景色组合起来,最后实例化单元格时应用。

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);

NumberFormatDateFormat来自jxl.write包,它们不会像String.format那样先把数值变成字符串再写入,而是以原始值 + 格式模板的形式存进 .xls。这样既能控制显示效果,又能保证 Excel 里单元格仍是数值类型,后续在 Excel 端做 SUMIFS 这类函数公式计算时不会因为文本类型而报错。

4.3 数据量上来时的分批写入和 flush

WritableWorkbookwrite()时才真正落盘,所以大批量导出时,所有单元格会积累在内存里。JXL 文档里提到的“分批写入”实际指的是每写满一批后先保证引用不释放,适时进行workbook.write()workbook.close()的配合,而不是提供一个公开的增量保存 API。

public static void exportLargeData(List<OrderVO> 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,优先检查三件事:是否在循环里反复创建 Workbook;WorkbookSettings 是否设置了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-excelcharset
老系统导出的文件某些中文单元格变问号原文件本身是 GBK 且 JXL 标称编码不支持用旧版系统重存或转换编码

针对第二点,常见的 Servlet 导出写法是:

response.setContentType("application/vnd.ms-excel;charset=UTF-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走新接口,兜底的异常信息提示用户“文件格式不支持”。这类兼容方法建议直接放在工具类内,方便全项目复用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询