文章目录
- 一、测试目标与原则
- 二、测试环境
- 三、项目结构
- [四、Maven 依赖(pom.xml)](#四、Maven 依赖(pom.xml))
- [五、LibreOffice 转 PDF 实现](#五、LibreOffice 转 PDF 实现)
- [六、docx4j 转 PDF 实现](#六、docx4j 转 PDF 实现)
- [七、性能统计工具(P50 / P95)](#七、性能统计工具(P50 / P95))
- 八、压测主程序(BenchMain)
- 九、实测结果
- 十、为什么差距这么大?
- 十一、结论
在 Java 后端开发中, Word 转 PDF 是一个非常常见的需求。
常见方案主要有两类:
- docx4j(纯 Java 实现)
- LibreOffice(调用 Office 原生引擎)
很多讨论停留在"听说 LibreOffice 更快",但没有代码、没有数据、无法复现。
用可运行的 Java 代码,验证 docx4j 和 LibreOffice 的真实性能差距。
一、测试目标与原则
测试目标
- 对比 单次转换耗时
- 对比 冷启动 / 热启动
- 对比 稳定性(失败率)
测试原则
- 同一批
.docx文件 - 串行执行(对比单次成本)
- 不引入 Spring / Web 干扰
- 用
System.nanoTime()直接计时
二、测试环境
- JDK:17
- CPU:8 Core
- 内存:16 GB
- OS:Windows
- LibreOffice:25.x
- docx4j:11.x
- 文档:20 页 Word(含图片、表格、页眉页脚、中文)
三、项目结构
text
pdf-bench/
├─ pom.xml
├─ samples/
│ └─ sample.docx
└─ src/main/java/com/example/bench/
├─ BenchMain.java
├─ LibreOfficeConverter.java
├─ Docx4jConverter.java
└─ Stats.java
四、Maven 依赖(pom.xml)
xml
<dependencies>
<!-- docx4j -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-core</artifactId>
<version>11.4.8</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>11.4.8</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>11.4.8</version>
</dependency>
<!-- Apache FOP -->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>2.9</version>
</dependency>
</dependencies>
五、LibreOffice 转 PDF 实现
java
public class LibreOfficeConverter {
private final String soffice;
public LibreOfficeConverter(String soffice) {
this.soffice = soffice;
}
public void convert(Path docx, Path outDir) throws Exception {
ProcessBuilder pb = new ProcessBuilder(
soffice,
"--headless",
"--nologo",
"--nolockcheck",
"--nodefault",
"--nofirststartwizard",
"--convert-to", "pdf",
"--outdir", outDir.toString(),
docx.toString()
);
pb.redirectErrorStream(true);
Process p = pb.start();
int code = p.waitFor();
if (code != 0) {
throw new RuntimeException("LibreOffice failed, exit=" + code);
}
}
}
⚠️ Windows 记得把
soffice改成
C:\Program Files\LibreOffice\program\soffice.exe
六、docx4j 转 PDF 实现
java
public class Docx4jConverter {
public void convert(Path docx, Path pdf) throws Exception {
// 1️⃣ 加载 Word
WordprocessingMLPackage wordMLPackage =
WordprocessingMLPackage.load(docx.toFile());
// 2️⃣ 字体映射(核心)
IdentityPlusMapper fontMapper = new IdentityPlusMapper();
// 宋体(核心兜底)
PhysicalFont simsun = PhysicalFonts.get("SimSun");
if (simsun == null) {
throw new RuntimeException("未找到 SimSun 字体,请确认系统已安装宋体");
}
fontMapper.put("SimSun", simsun);
// ===== 常用中文字体映射 =====
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft YaHei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
// ===== 修复 "宋体(正文)/ 宋体(标题)/ 台湾字体" 乱码 =====
PhysicalFonts.put("PMingLiU", simsun);
PhysicalFonts.put("新細明體", simsun);
// 3️⃣ 设置到 Word 包
wordMLPackage.setFontMapper(fontMapper);
// 4️⃣ 转 PDF
try (OutputStream os = Files.newOutputStream(pdf)) {
Docx4J.toPDF(wordMLPackage, os);
}
}
}
docx4j 走的是 XSL-FO + Apache FOP 路线。
七、性能统计工具(P50 / P95)
java
public class Stats {
private final List<Long> times = new ArrayList<>();
public void add(long nanos) {
times.add(nanos);
}
public void print(String name) {
Collections.sort(times);
double avg = times.stream().mapToLong(x -> x).average().orElse(0) / 1e6;
double p50 = times.get(times.size() / 2) / 1e6;
double p95 = times.get((int)(times.size() * 0.95)) / 1e6;
System.out.printf(
"%s -> avg=%.2fms, p50=%.2fms, p95=%.2fms%n",
name, avg, p50, p95
);
}
}
八、压测主程序(BenchMain)
java
public class BenchMain {
public static void main(String[] args) throws Exception {
Path docx = Path.of("samples/sample.docx");
Path out = Path.of("out");
Files.createDirectories(out);
LibreOfficeConverter lo =
new LibreOfficeConverter("soffice");
Docx4jConverter d4j =
new Docx4jConverter();
int rounds = 5;
// LibreOffice
Stats loStats = new Stats();
for (int i = 0; i < rounds; i++) {
long t0 = System.nanoTime();
lo.convert(docx, out);
loStats.add(System.nanoTime() - t0);
}
// docx4j
Stats d4jStats = new Stats();
for (int i = 0; i < rounds; i++) {
long t0 = System.nanoTime();
d4j.convert(docx, out.resolve("d4j_" + i + ".pdf"));
d4jStats.add(System.nanoTime() - t0);
}
loStats.print("LibreOffice");
d4jStats.print("docx4j");
}
}
九、实测结果

👉 ** LibreOffice 稳定快 **
十、为什么差距这么大?
docx4j 执行路径
text
docx(xml)
→ JAXB
→ Java 对象
→ XSL-FO
→ Apache FOP 排版
→ PDF
- 排版在 Java 层完成
- 表格、分页、字体极其耗 CPU
- GC 压力大
LibreOffice 执行路径
text
docx
→ Office 原生排版引擎
→ PDF
- 原生排版
- 无 XML 重建
- 几乎等同"另存为 PDF"
十一、结论
如果你是 Java 后端,做真实业务文档转换:
- 性能:LibreOffice ✅
- 稳定性:LibreOffice ✅
- 还原度:LibreOffice ✅
docx4j 只适合:
- 简单模板
- 低 QPS
- 必须纯 Java 的场景
docx4j导出来可能有问题

LibreOffice 正常
