在 Linux 容器里優(yōu)雅地導(dǎo)出 Excel:徹底擺脫 fontconfig 的曲折之旅

1. 背景:為什么容器里會(huì)突然報(bào) fontconfig?

  • FastExcel / EasyExcel流式寫 (SXSSF) 時(shí),內(nèi)部會(huì)調(diào)用
    sheet.trackAllColumnsForAutoSizing()
  • 該方法立即 new AutoSizeColumnTrackerjava.awt.Font
    FontManagerfontconfig
  • 精簡 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 == null NPE
  • 邏輯只依賴 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é)

  1. 罪魁禍?zhǔn)?/strong>:sheet.trackAllColumnsForAutoSizing()

  2. 核心對策:讓代碼路徑不走這行(內(nèi)存模式 / 模板 / fork)

  3. 列寬方案:自寫 Handler 字符計(jì)數(shù),既輕量又“夠好看”

  4. FastExcel & EasyExcel 同理,只要避開 track…() 就再也不會(huì)碰字體

  5. 最終好處

    • 鏡像 不再安裝字體包,體積更小
    • Alpine、Distroless、Oracle Linux Slim 部署 穩(wěn)定無錯(cuò)
    • 仍保留 SXSSF 的低內(nèi)存優(yōu)勢(路線 B/C)

一句話總結(jié)
把“想讓服務(wù)器自己測列寬”的念頭交給客戶端(Excel),
或者用最簡單的字符長度估算——
只要在服務(wù)端徹底繞開 AutoSizeColumnTracker
fontconfig 就會(huì)永遠(yuǎn)消失。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容