简介这份资源是一个基于 Java 的 XLS 转换示例程序面向需要将 Excel 电子表格批量导出为 TXT 或 CSV 的开发者尤其适合正在学习 Apache POI 读写 Excel 的初中级 Java 程序员。它通过一个可运行的示例演示如何借助 Apache POI 的 API 逐行读取 XLS 内容并输出为纯文本或逗号分隔文件帮助读者理解单元格遍历、数据类型处理与文件写出等核心环节。压缩包内共 2 个文件包含 1 个 Java 源码文件和 1 个 Markdown 说明文档源码承载转换逻辑说明文档交代使用方式与注意事项整体仅约 2KB轻量易读。作者在描述中特别指出示例针对特定格式的 XLS 编写存在部分硬编码的列号读者可通过不指定具体列号来规避这一限制这一提示对实际改造很有参考价值。目前已有 191 人学习适合作为 Excel 转文本工具的入门参考与二次开发起点。1. 从一堆“打不开”的 XLS 说起这个 Java 小工具到底能干什么手里攒了几十上百个 XLS 文件来源五花八门——老系统导出的、别人邮件发来的、爬虫落盘的格式横跨 xls、xlsx甚至还有改了后缀名的伪 Excel。想批量喂给下游脚本做清洗第一步就卡住Python 的 pandas 读老式 xls 要装 xlrd读 xlsx 要 openpyxl遇到加密或格式错乱直接抛异常一个文件崩掉整批任务。XLS2TXT 就是冲着这个场景来的一个用 Java 写的转换工具把任意 XLS 文件统一转成 TXT 和 CSV屏蔽掉底层格式差异让下游只面对纯文本。它适合做数据预处理、ETL 前置清洗、批量报表归档的人也适合不想在每台机器上折腾 Python 依赖的 Java 后端。核心价值一句话把“格式兼容”这件脏活收进一个 jar 里你只管拿结果。2. 拆开看XLS2TXT 的转换链路与 Java 依赖选型2.1 为什么用 Java 而不是脚本语言批量文件转换这类活脚本语言写起来快但部署时容易翻车。目标机器上 Python 版本、pip 源、C 扩展编译环境任何一环出问题都得现场救火。Java 的优势在于打包成可执行 jar 后只要目标机器有 JREjava -jar就能跑依赖全在 jar 内部不存在“我这能跑你那不能跑”的玄学。XLS2TXT 走的就是这条路把 Apache POI 这类解析库连同业务逻辑一起打进 fat jar分发时只给一个文件。另一个理由是并发。批量转换几百个文件时Java 的线程池和 NIO 文件操作比脚本语言更可控尤其是要限制内存占用、避免一次性把大表读进堆的情况。常见做法是用固定大小线程池加阻塞队列生产者扫目录、消费者解析写文件内存曲线比单线程顺序处理平稳得多。2.2 解析层POI 的 HSSF 与 XSSF 怎么选XLS 分两代老式.xls是二进制格式对应 POI 的 HSSF新式.xlsx是 OOXML 压缩包对应 XSSF。XLS2TXT 要“通吃”就得在入口处判断文件真实类型而不是看后缀。判断方法读文件头魔数.xls开头是D0 CF 11 E0.xlsx开头是50 4B 03 04也就是 ZIP 的 PK 头。只看后缀会把改了名的文件送错解析器直接抛OfficeXmlFileException或NotOLE2FileException。// 通过文件头魔数判断真实格式避免被错误后缀误导 public static WorkbookType detectType(File file) throws IOException { try (FileInputStream fis new FileInputStream(file)) { byte[] header new byte[8]; int n fis.read(header); if (n 4) return WorkbookType.UNKNOWN; // xls 的 OLE2 复合文档头 if (header[0] (byte) 0xD0 header[1] (byte) 0xCF header[2] (byte) 0x11 header[3] (byte) 0xE0) { return WorkbookType.XLS; } // xlsx 本质是 zipPK 头 if (header[0] 0x50 header[1] 0x4B header[2] 0x03 header[3] 0x04) { return WorkbookType.XLSX; } return WorkbookType.UNKNOWN; } }这段逻辑的关键在header数组的前四字节比对。0xD0CF11E0是 OLE2 复合文档的固定签名0x504B0304是 ZIP 本地文件头。参数上唯一要注意的是读取长度至少读 4 字节读 8 字节是为了留余量。如果文件小于 4 字节直接判为 UNKNOWN不要硬解析否则 POI 会抛底层异常堆栈很难看。2.3 写出层TXT 与 CSV 的编码和分隔符陷阱转换的终点是文本但文本有两个隐形坑编码和分隔符。TXT 输出建议统一 UTF-8带不带 BOM 取决于下游。如果下游是 Windows 上的 Excel 直接打开不带 BOM 的 UTF-8 中文会乱码这时加 BOM 是常见做法如果下游是 Linux 脚本或数据库导入BOM 反而会污染第一列字段名。XLS2TXT 一般把编码做成可配参数默认 UTF-8 无 BOM。CSV 的坑更集中。字段里如果本身含逗号、换行、双引号必须按 RFC 4180 转义整个字段用双引号包起来字段内的双引号写成两个双引号。很多手写转换器只做join(,)遇到地址栏里的逗号就列错位这种翻车在批量数据里特别隐蔽往往到下游才发现。// CSV 字段转义处理逗号、换行、双引号 public static String escapeCsv(String field) { if (field null) return ; boolean needQuote field.contains(,) || field.contains(\n) || field.contains(\r) || field.contains(\); if (!needQuote) return field; // 字段内双引号翻倍再整体加引号 return \ field.replace(\, \\) \; }needQuote判断四种情况逗号、换行、回车、双引号。只要命中一种就加引号包裹。replace(\, \\)把单个双引号替换成两个这是 RFC 4180 的硬性要求。参数上没什么可调的但要注意顺序先判断再替换不能先替换后判断否则原本不含引号的字段会被误加引号。2.4 空单元格与合并单元格的处理策略Excel 里空单元格和合并单元格是转换质量的分水岭。POI 遍历行时row.getCell(i)对不存在的单元格返回 null直接toString()会 NPE。合并单元格更麻烦只有左上角那个格子有值其余返回空。如果直接按行列输出合并区域会变成一堆空字段下游根本还原不出原表结构。常见做法是先把合并区域读出来建一个坐标到值的映射遍历时优先查映射查不到再取单元格本身的值。空单元格统一输出空字符串而不是null或跳过整行——跳过会导致行号错位下游对不上原始行。// 预读合并区域建立 (row,col) - value 映射 MapString, String mergedMap new HashMap(); for (int i 0; i sheet.getNumMergedRegions(); i) { CellRangeAddress region sheet.getMergedRegion(i); Row firstRow sheet.getRow(region.getFirstRow()); if (firstRow null) continue; Cell firstCell firstRow.getCell(region.getFirstColumn()); String val firstCell null ? : getCellString(firstCell); // 把整个合并区域每个坐标都填上左上角的值 for (int r region.getFirstRow(); r region.getLastRow(); r) { for (int c region.getFirstColumn(); c region.getLastColumn(); c) { mergedMap.put(r , c, val); } } }CellRangeAddress给出合并区域的起止行列。mergedMap的 key 用行,列拼接简单但够用。填充时把整个矩形区域每个坐标都映射到左上角的值这样遍历时无论走到哪个格子都能拿到正确内容。代价是合并区域大时内存占用上升一般表格规模下可以接受。3. 动手跑起来命令行参数、批量模式与输出配置3.1 环境准备与最小可运行示例跑 XLS2TXT 的前提是目标机器有 JRE 8 或以上。先确认版本java -version # 期望输出类似 openjdk version 1.8.0_xxx 或更高如果版本低于 8POI 较新版本会直接报UnsupportedClassVersionError。确认后最小运行命令是单文件转换# 单文件转换输入 xls输出同名 txt 和 csv java -jar xls2txt.jar -i ./data/report.xls -o ./out -f txt,csv参数说明-i指定输入文件可以是单个文件也可以是目录-o指定输出目录不存在会自动创建-f指定输出格式逗号分隔支持txt、csv不传默认两个都出。这里有个细节输出文件名默认沿用输入文件名只换后缀所以report.xls会生成report.txt和report.csv不会覆盖原始文件。3.2 批量转换与并发参数怎么设批量模式把-i指向目录即可工具会递归扫描。并发相关参数一般有三个线程数、批大小、是否跳过已存在输出。# 批量转换4 线程每批 50 个文件跳过已转换的 java -jar xls2txt.jar -i ./data -o ./out -f csv \ --threads 4 --batch-size 50 --skip-existing--threads控制解析线程数经验值是 CPU 核数的 1 到 2 倍。设太大反而因为磁盘 IO 争抢变慢设太小又吃不满多核。--batch-size是每批提交给线程池的任务数影响内存峰值批越大同时驻留的 Workbook 对象越多。--skip-existing在断点续跑时特别有用输出文件已存在就跳过避免重复劳动。如果内存吃紧把--batch-size降到 10 到 20配合-Xmx限制堆大小。# 限制堆内存 512M适合小内存机器跑大批量 java -Xmx512m -jar xls2txt.jar -i ./data -o ./out --threads 2 --batch-size 10-Xmx512m是 JVM 参数必须放在-jar之前放后面会被当成程序参数。这是新手最容易踩的坑之一命令不报错但参数没生效。3.3 输出格式的细粒度控制TXT 和 CSV 的输出行为可以通过参数微调。常见的有分隔符、编码、是否输出表头、空行处理。参数作用默认值建议--delimiterCSV 列分隔符,数据含逗号多时改\t--encoding输出编码UTF-8Windows 下游可改GBK--bom是否写 BOMfalseExcel 直接打开时设true--header是否输出首行表头true无表头数据设false--sheet指定工作表全部多 sheet 时按名或索引选# 输出制表符分隔、带 BOM、只转第一个 sheet java -jar xls2txt.jar -i ./data -o ./out -f csv \ --delimiter \t --bom true --sheet 0--delimiter \t在 shell 里要写成转义形式实际传入的是制表符。--sheet 0表示只转索引为 0 的工作表多 sheet 文件默认会全部转出文件名加 sheet 名后缀如果 sheet 名含非法字符会被替换成下划线。3.4 转换结果怎么验证转完不能直接信得抽验。最直接的办法是比对行数和抽样字段。行数用wc -l快速看# 统计输出 CSV 行数和原始表预期行数比对 wc -l ./out/report.csv行数对不上通常有三种原因空行被跳过、合并单元格展开导致行数变化、或者原表有隐藏行。抽样验证建议挑含特殊字符的字段比如地址、备注看 CSV 里是否正确转义。如果下游是数据库直接建临时表导入让数据库的严格模式帮你抓格式错误比肉眼靠谱。4. 避坑指南批量转换里最容易翻车的五个点4.1 现象转换报 NotOLE2FileException但文件后缀是 xls原因文件实际是 xlsx 或 HTML 伪装的只是后缀被改成了 xls。POI 按后缀选了 HSSF 解析器读到 ZIP 头直接抛异常。解决入口处用 2.2 节的魔数判断真实类型不要信后缀。如果魔数既不是 OLE2 也不是 ZIP尝试按纯文本或 HTML 表格解析仍失败就记录到错误清单不要中断整批任务。4.2 现象中文全部乱码英文正常原因输出编码和下游读取编码不一致。工具默认 UTF-8下游用 GBK 打开中文必然乱码。解决确认下游编码。Windows Excel 直接打开就加--bom true并保持 UTF-8老系统只认 GBK 就设--encoding GBK。不要两头都改改一头对齐即可。4.3 现象CSV 列错位某些行字段数对不上原因字段内含逗号或换行没做 RFC 4180 转义被下游按分隔符切碎了。解决确保写出层走了 2.3 节的escapeCsv。已经转好的文件可以用脚本补救但更稳的是重转。验证方法是统计每行字段数出现不一致就说明有未转义字段。4.4 现象大批量跑到一半 OOM原因--batch-size太大或线程数太多同时驻留的 Workbook 对象撑爆堆内存。POI 的 XSSF 尤其吃内存因为它会把整个 xlsx 解压进内存。解决降--batch-size到 10 到 20降--threads并显式设-Xmx。如果单个文件本身就巨大考虑换 POI 的流式 APISXSSF 用于写XSSFReader 用于读但那是改造工具本身的活。4.5 现象合并单元格转出来一片空白原因只取了左上角单元格其余坐标返回 null 或空没有做区域填充。解决按 2.4 节预读合并区域建映射。注意映射要在遍历前建好遍历中动态查 POI 的getMergedRegion性能很差大表会明显变慢。5. 进阶把 XLS2TXT 嵌进自己的 Java 流水线工具用命令行跑只是入门真正省事的是把它当库嵌进现有 Java 项目。核心是把转换逻辑抽成一个服务类输入File输出ConversionResult里面带输出路径、行数、耗时和错误信息。这样你可以用自己的调度框架控制并发用日志框架收集错误而不是解析命令行输出。// 把转换封装成可复用服务便于嵌入现有流水线 public class XlsConversionService { public ConversionResult convert(File input, File outDir, ConvertOptions opts) { long start System.currentTimeMillis(); ConversionResult result new ConversionResult(); result.setInput(input); try { WorkbookType type TypeDetector.detectType(input); if (type WorkbookType.UNKNOWN) { result.setError(无法识别的文件格式); return result; } try (Workbook wb WorkbookFactory.create(input)) { // 按配置逐 sheet 写出统计行数 int rows SheetWriter.write(wb, outDir, opts); result.setRowCount(rows); } } catch (Exception e) { result.setError(e.getMessage()); } result.setCostMs(System.currentTimeMillis() - start); return result; } }WorkbookFactory.create会自动根据文件内容选 HSSF 还是 XSSF省去手动分支。ConvertOptions封装编码、分隔符、sheet 选择等参数和命令行参数一一对应。ConversionResult把错误吞进对象而不是抛异常这样批量处理时单个文件失败不影响整批错误集中收集后统一处理。验证嵌入效果有个实用技巧拿一批已知结果的样本文件跑回归比对输出文件的 MD5。转换逻辑稳定的话同样输入同样参数输出应该逐字节一致。一旦某次改动导致 MD5 变化说明写出行为变了得查是转义逻辑动了还是编码默认值改了。我自己的习惯是任何批量转换任务上线前先拿 20 个覆盖各种边界空表、合并单元格、含逗号字段、超大表、伪后缀的样本跑一遍把结果和预期比对通过再放量。这个习惯帮我拦下过好几次“看着能跑、实际列错位”的问题。从那以后我每次改写出逻辑都强制走一遍样本回归不敢省。希望帮到你。本文还有配套的精品资源点击获取