Java 讀取 Word 文件(.doc/.docx)是企業級應用常見需求,如報表解析、合約審核、文檔轉換。主流方案分為兩大陣營:Apache POI(官方標準、功能完整、支援 .doc/.docx)與 RTF 純文字處理(無依賴、輕量、適合簡單模板替換)。本文深度解析兩種方案的適用場景、核心 API 與實戰代碼。
| 維度 | Apache POI | RTF 字串替換 |
|---|---|---|
| 支援格式 | .doc (HWPF) / .docx (XWPF) | .rtf 僅限 |
| 依賴大小 | ~15MB (poi-ooxml) | 零依賴 (JDK 內建) |
| 解析能力 | 完整結構 (段落、表格、樣式、圖片) | 純文字+簡單格式標記 |
| 效能 | 較慢、記憶體佔用大 | 極快、記憶體佔用極小 |
| 適用場景 | 複雜文檔解析、格式保真轉換 | 固定模板填充、簡單報表生成 |
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
import org.apache.poi.xwpf.usermodel.*;
import java.io.FileInputStream;
import java.util.List;
public class DocxReader {
public static void readDocx(String filePath) throws Exception {
try (FileInputStream fis = new FileInputStream(filePath);
XWPFDocument doc = new XWPFDocument(fis)) {
// 1. 讀取段落文字
System.out.println("=== 段落內容 ===");
for (XWPFParagraph para : doc.getParagraphs()) {
System.out.println(para.getText());
}
// 2. 讀取表格
System.out.println("\n=== 表格內容 ===");
for (XWPFTable table : doc.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
System.out.print("[" + cell.getText() + "] ");
}
System.out.println();
}
System.out.println("--- 分隔 ---");
}
// 3. 讀取圖片關係 (進階)
// List pictures = doc.getAllPictures();
}
}
}
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.*;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import java.io.FileInputStream;
public class DocReader {
public static void readDoc(String filePath) throws Exception {
try (FileInputStream fis = new FileInputStream(filePath);
POIFSFileSystem fs = new POIFSFileSystem(fis);
HWPFDocument doc = new HWPFDocument(fs)) {
Range range = doc.getRange();
// 讀取所有文字
System.out.println("全文文字:");
System.out.println(range.text());
// 遍歷表格
TableIterator it = new TableIterator(range);
while (it.hasNext()) {
Table table = (Table) it.next();
for (int r = 0; r < table.numRows(); r++) {
TableRow row = table.getRow(r);
for (int c = 0; c < row.numCells(); c++) {
TableCell cell = row.getCell(c);
for (int p = 0; p < cell.numParagraphs(); p++) {
Paragraph para = cell.getParagraph(p);
System.out.print("[" + para.text() + "] ");
}
}
System.out.println();
}
}
}
}
}
RTF (Rich Text Format) 是純文字格式,控制碼為反斜線開頭 (如 \b 粗體、\par 換行)。核心思路:將動態內容轉為 RTF 編碼,替換模板佔位符。
public class RtfEncoder {
private static final char[] HEX = "0123456789ABCDEF".toCharArray();
/** 將純文字轉為 RTF 十六進位編碼 (如 "中文" -> \'d6\'d0\'ce\'c4) */
public static String encode(String text) {
if (text == null) return "";
StringBuilder sb = new StringBuilder();
byte[] bytes = text.getBytes(StandardCharsets.UTF_8); // 或 Big5 視模板編碼
for (byte b : bytes) {
sb.append("\\'").append(HEX[(b >> 4) & 0xF]).append(HEX[b & 0xF]);
}
return sb.toString();
}
/** 讀取 RTF 模板檔案為字串 */
public static String readTemplate(String path) throws IOException {
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
}
/** 替換佔位符並輸出新檔案 */
public static void generate(String template, Map placeholders, String outputPath)
throws IOException {
String result = template;
for (Map.Entry entry : placeholders.entrySet()) {
String encoded = encode(entry.getValue());
result = result.replace(entry.getKey(), encoded);
}
Files.write(Paths.get(outputPath), result.getBytes(StandardCharsets.UTF_8));
}
}
public class ContractGenerator {
public static void main(String[] args) throws Exception {
// 1. 讀取模板 (模板內含 ${partyA}、${partyB}、${amount} 等佔位符)
String template = RtfEncoder.readTemplate("template/contract.rtf");
// 2. 準備動態資料
Map data = new HashMap<>();
data.put("${partyA}", "甲方公司股份有限公司");
data.put("${partyB}", "乙方科技有限公司");
data.put("${amount}", "新臺幣壹佰萬元整 (NT$1,000,000)");
data.put("${date}", "2024年12月25日");
// 3. 生成合約
RtfEncoder.generate(template, data, "output/contract_20241225.rtf");
System.out.println("合約生成完成!");
}
}
\ansicpg950 (Big5) 或 \ansicpg936 (GBK) 標記編碼new String(bytes, "Big5")// XWPFDocument 預設載入整份文件到記憶體,大檔案易 OOM // 方案:使用 SAX 事件驅動解析 (XWPFDocument 無直接支援,需自行解析 word/document.xml) // 或拆分文件、增加 JVM Heap (-Xmx2g)
cell.getCTTc().getTcPr().getGridSpan() 取得橫向合併vMerge 屬性// .docx 圖片提取
for (XWPFPictureData pic : doc.getAllPictures()) {
String ext = pic.suggestFileExtension(); // png/jpeg/emf...
byte[] data = pic.getData();
Files.write(Paths.get("img_" + System.currentTimeMillis() + "." + ext), data);
}