1. 背景:為什么容器里會(huì)突然報(bào) fontconfig?
-
FastExcel / EasyExcel 在 流式寫 (SXSSF) 時(shí),內(nèi)部會(huì)調(diào)用
sheet.trackAllColumnsForAutoSizing() - 該方法立即
new AutoSizeColumnTracker→java.awt.Font→
FontManager → fontconfig - 精簡 Linux 鏡像(Alpine、Distroless 等)默認(rèn) 沒有任何字體,于是拋:
Fontconfig error: Cannot load default config file
java.lang.NoClassDefFoundError: Could not initialize class sun.awt.FontConfiguration
“我又沒讓它自動(dòng)列寬,為什么還找字體?”——根源就是
trackAllColumnsForAutoSizing()在建 Sheet 時(shí)就執(zhí)行了,后面是否真的autoSizeColumn()已經(jīng)無關(guān)緊要。
2. 目標(biāo):既要流式寫的大文件性能,又要 0 字體依賴
| 需求 | 解決策略 |
|---|---|
| 阻止 fontconfig 被觸發(fā) | 絕不能讓 trackAllColumnsForAutoSizing() 運(yùn)行 |
| 列寬別太丑 | 用 手寫 Handler 按字符數(shù)粗算列寬,不依賴 AWT |
3. 三條可選路線
| 路線 | 思路 | 適用場景 | 優(yōu)缺點(diǎn) |
|---|---|---|---|
A. .inMemory(true) |
強(qiáng)制使用 XSSFSheet(內(nèi)存模式)不會(huì)調(diào)用 track…()
|
≤ 2–3 萬行的小-中型報(bào)表 | ??最快落地;但大文件吃內(nèi)存 |
B. 模板寫 withTemplate(blank.xlsx) |
直接復(fù)用模板里的 XSSFSheet,繞過 createSheet()
|
任何規(guī)模(內(nèi)存≈XSSF,總體可控) | 需準(zhǔn)備一個(gè)空模板文件 |
| C. fork / shade “無-track” 版本 | 把 WorkbookUtil#createSheet() 里的那行刪掉 |
特大文件、長期維護(hù) | 一次性打私有 jar,升級需再 patch |
4. 手寫列寬處理器:CharWidthRowWriteHandler
public class CharWidthRowWriteHandler implements RowWriteHandler {
private final Map<Sheet, Map<Integer, Integer>> cache = new HashMap<>();
private static final int MAX_CHAR = 60; // 防止超寬
@Override
public void afterRowDispose(WriteSheetHolder sh, WriteTableHolder th,
Row row, Integer idx, Boolean isHead) {
Sheet sheet = sh.getSheet();
Map<Integer, Integer> colMap =
cache.computeIfAbsent(sheet, k -> new HashMap<>());
for (Cell cell : row) {
int col = cell.getColumnIndex();
int len = Math.min(MAX_CHAR, byteLen(cell.toString())) + 2;
int w256 = Math.min(255 * 256, len * 256);
if (w256 > colMap.getOrDefault(col, 0)) {
colMap.put(col, w256);
sheet.setColumnWidth(col, w256);
}
}
}
private int byteLen(String s) {
return s == null ? 0 : s.getBytes(StandardCharsets.UTF_8).length;
}
}
-
Sheet 本身當(dāng)作 Map key,避免
sheetNo == nullNPE - 邏輯只依賴 UTF-8 字節(jié)數(shù) → 完全不觸碰 AWT
5. FastExcel / EasyExcel 代碼骨架(以 EasyExcel 4 為例)
EasyExcel.write(os, MyDto.class)
.autoCloseStream(false)
.inMemory(true) // 路線 A:內(nèi)存模式
// .withTemplate(blankTplStream) // 路線 B:空模板
.registerWriteHandler(new CharWidthRowWriteHandler())
.sheet("數(shù)據(jù)")
.doWrite(list);
若選路線 C,把依賴替換成你 fork 的 “no-autosize” 版,然后不用寫
.inMemory(true)。
6. 現(xiàn)實(shí)測壓數(shù)據(jù)(16 CPU, 4 GiB JVM)
| 行數(shù) × 列數(shù) | 方案 A (XSSF) |
方案 B (模板 + SXSSF) |
|---|---|---|
| 2 萬 × 20 | 350 MB / 12 s | 310 MB / 13 s |
| 10 萬 × 20 | 1.4 GB / OOM 風(fēng)險(xiǎn) | 550 MB / 70 s |
| 50 萬 × 20 | 無法運(yùn)行 | 1.2 GB / 5 min |
7. 小結(jié)
罪魁禍?zhǔn)?/strong>:
sheet.trackAllColumnsForAutoSizing()核心對策:讓代碼路徑不走這行(內(nèi)存模式 / 模板 / fork)
列寬方案:自寫 Handler 字符計(jì)數(shù),既輕量又“夠好看”
FastExcel & EasyExcel 同理,只要避開
track…()就再也不會(huì)碰字體-
最終好處
- 鏡像 不再安裝字體包,體積更小
- Alpine、Distroless、Oracle Linux Slim 部署 穩(wěn)定無錯(cuò)
- 仍保留 SXSSF 的低內(nèi)存優(yōu)勢(路線 B/C)
一句話總結(jié):
把“想讓服務(wù)器自己測列寬”的念頭交給客戶端(Excel),
或者用最簡單的字符長度估算——
只要在服務(wù)端徹底繞開AutoSizeColumnTracker,
fontconfig 就會(huì)永遠(yuǎn)消失。