最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java?POI讀取Excel所需全部依賴包的實(shí)戰(zhàn)指南

 更新時(shí)間:2025年12月11日 09:26:56   作者:高天艷陽  
Java?POI是處理Microsoft?Office文件的開源庫,尤其適用于讀取和操作Excel文件,本文詳細(xì)介紹了使用Java?POI讀取Excel時(shí)必須導(dǎo)入的核心JAR包及其作用,需要的朋友可以參考下

簡介:

Java POI是處理Microsoft Office文件的開源庫,尤其適用于讀取和操作Excel文件。本文詳細(xì)介紹了使用Java POI讀取Excel時(shí)必須導(dǎo)入的核心JAR包及其作用,包括支持.xls和.xlsx格式的主庫、XML解析依賴、OOXML架構(gòu)支持以及輔助工具包。正確配置這些依賴(如poi-3.7.jar、poi-ooxml-schemas、xmlbeans、dom4j等)對(duì)程序正常運(yùn)行至關(guān)重要,缺失任一組件可能導(dǎo)致運(yùn)行時(shí)異常。此外,還提供了示例與擴(kuò)展功能包,便于學(xué)習(xí)和集成到構(gòu)建流程中。

Java POI 深度解析:從基礎(chǔ)操作到企業(yè)級(jí)實(shí)戰(zhàn)的全鏈路指南

在現(xiàn)代企業(yè)級(jí)應(yīng)用中,Excel 已不僅是辦公工具,更成為數(shù)據(jù)流轉(zhuǎn)的核心載體。無論是財(cái)務(wù)報(bào)表、用戶行為日志,還是供應(yīng)鏈清單,這些結(jié)構(gòu)化數(shù)據(jù)常常以 .xlsx .xls 的形式穿梭于系統(tǒng)之間。而 Java 作為后端開發(fā)的主力語言,如何高效、穩(wěn)定地處理這些文件?Apache POI 正是這一問題的標(biāo)準(zhǔn)答案。

但現(xiàn)實(shí)遠(yuǎn)比“調(diào)用 API”復(fù)雜得多。你是否曾遇到過這樣的場(chǎng)景:

  • 導(dǎo)入一個(gè) 50MB 的訂單表,JVM 瞬間拋出 OutOfMemoryError ;
  • 解析單元格時(shí)發(fā)現(xiàn)本該是日期的數(shù)字變成了科學(xué)計(jì)數(shù)法字符串;
  • 多個(gè)模塊依賴不同版本的 POI,運(yùn)行時(shí)報(bào)出 NoSuchMethodError ;
  • 明明代碼邏輯正確,卻因?yàn)槟硞€(gè)隱藏的 XML 節(jié)點(diǎn)導(dǎo)致解析失敗……

這些問題背后,往往不是 API 使用不當(dāng),而是對(duì) POI 底層機(jī)制缺乏深度理解。今天,我們就來徹底拆解這套被無數(shù)項(xiàng)目依賴的技術(shù)棧——從最基礎(chǔ)的對(duì)象模型,到流式處理的性能瓶頸,再到輔助依賴包之間的隱秘協(xié)作,帶你走進(jìn)一個(gè)真正“可控”的 Excel 自動(dòng)化世界。

核心架構(gòu)設(shè)計(jì):HSSF 與 XSSF 的雙生子之謎

當(dāng)我們說“用 Java 讀寫 Excel”,其實(shí)是在和兩個(gè)截然不同的技術(shù)體系打交道: HSSF XSSF 。它們分別對(duì)應(yīng)兩種完全不同的文件格式—— .xls .xlsx ,雖然對(duì)外提供相似的接口,但內(nèi)部實(shí)現(xiàn)天差地別。

HSSFWorkbook vs XSSFWorkbook:二進(jìn)制與 XML 的分水嶺

// 處理老式 .xls 文件
Workbook hssf = new HSSFWorkbook(new FileInputStream("data.xls"));

// 處理新版 .xlsx 文件  
Workbook xssf = new XSSFWorkbook(OPCPackage.open("data.xlsx"));

看起來很像?別被表象迷惑了。這兩行代碼背后的加載過程完全不同。

維度HSSF(.xls)XSSF(.xlsx)
存儲(chǔ)結(jié)構(gòu)二進(jìn)制 BIFF 格式ZIP 壓縮包 + XML
最大行數(shù)65,536 行1,048,576 行
內(nèi)存模型半懶加載全量 DOM 加載
初始化速度快(直接解析字節(jié)流)慢(需解壓并解析多個(gè) XML)

HSSF 面對(duì)的是 Excel 97–2003 年代遺留下來的二進(jìn)制格式(BIFF),它的優(yōu)點(diǎn)是緊湊、解析快;缺點(diǎn)也很明顯——功能受限、容量小、不支持現(xiàn)代特性如圖表或條件格式。

而 XSSF 則基于 Office Open XML (OOXML) 標(biāo)準(zhǔn)構(gòu)建,本質(zhì)上是一個(gè)符合 OPC(Open Packaging Conventions)規(guī)范的 ZIP 歸檔文件。這意味著你可以把它當(dāng)成普通壓縮包打開:

unzip -l report.xlsx

你會(huì)看到類似這樣的目錄結(jié)構(gòu):

xl/
├── workbook.xml           # 工作簿元信息
├── worksheets/sheet1.xml  # 實(shí)際表格數(shù)據(jù)
├── sharedStrings.xml      # 全局字符串池
└── styles.xml             # 樣式定義
[Content_Types].xml        # MIME 類型映射
_rels/.rels                # 關(guān)系描述符

這正是 XSSF 強(qiáng)大之處:它將復(fù)雜的電子文檔分解為可獨(dú)立訪問的組件。但也正因如此,當(dāng)你使用 new XSSFWorkbook() 時(shí),POI 會(huì)一次性把這些 XML 文件全部加載進(jìn)內(nèi)存,并轉(zhuǎn)換成 DOM 樹節(jié)點(diǎn)。哪怕你只想讀取第一行數(shù)據(jù),整個(gè)文檔也得完整駐留堆中。

小貼士:如果你正在維護(hù)一個(gè)老系統(tǒng),且只處理小于 10MB 的 .xls 文件,HSSF 依然是輕量高效的首選。但對(duì)于任何新項(xiàng)目,尤其是涉及大數(shù)據(jù)量的場(chǎng)景,請(qǐng)果斷轉(zhuǎn)向 .xlsx + XSSF/SXSSF 技術(shù)棧。

對(duì)象模型探秘:Workbook → Sheet → Row → Cell 的層級(jí)迷宮

無論你是操作 .xls 還是 .xlsx ,POI 都抽象出統(tǒng)一的對(duì)象模型:

Workbook → Sheet → Row → Cell

這個(gè)看似簡單的四層結(jié)構(gòu),構(gòu)成了所有 Excel 操作的基礎(chǔ)路徑。我們來看一段典型的遍歷代碼:

Sheet sheet = workbook.getSheetAt(0);
for (int i = 0; i <= sheet.getLastRowNum(); i++) {
    Row row = sheet.getRow(i);
    if (row == null) continue;

    for (int j = 0; j < row.getLastCellNum(); j++) {
        Cell cell = row.getCell(j, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
        System.out.print(getCellValue(cell) + "\t");
    }
    System.out.println();
}

這段代碼雖然簡潔,但藏著不少坑。比如:

  • getLastRowNum() 返回的是 物理存在的最后一行索引 ,中間可能有空行;
  • getRow(i) 可能返回 null ,必須判空;
  • getCell(j) 默認(rèn)策略是返回 null ,如果不設(shè)置 MissingCellPolicy ,極易引發(fā) NPE;
  • 單元格類型不確定,直接調(diào)用 getStringCellValue() 在非字符串類型上會(huì)拋異常!

所以更健壯的做法是封裝一個(gè)通用的取值方法:

public static Object getCellValue(Cell cell) {
    if (cell == null) return "";

    switch (cell.getCellType()) {
        case STRING:
            return cell.getStringCellValue().trim();
        case NUMERIC:
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue();
            } else {
                return BigDecimal.valueOf(cell.getNumericCellValue())
                                .stripTrailingZeros()
                                .toPlainString(); // 避免科學(xué)計(jì)數(shù)法
            }
        case BOOLEAN:
            return cell.getBooleanCellValue();
        case FORMULA:
            FormulaEvaluator evaluator = cell.getSheet()
                                             .getWorkbook()
                                             .getCreationHelper()
                                             .createFormulaEvaluator();
            return evaluateFormula(evaluator.evaluate(cell));
        default:
            return "";
    }
}

注意這里用了 BigDecimal 而不是 double ,特別適用于金額字段,避免浮點(diǎn)精度丟失問題。同時(shí)通過 DataFormatter 或公式求值器進(jìn)一步提升兼容性。

下面這張 Mermaid 圖清晰展示了對(duì)象間的導(dǎo)航關(guān)系:

graph TD
    A[Workbook] --> B[Sheet 1]
    A --> C[Sheet 2]
    B --> D[Row 0]
    B --> E[Row 1]
    D --> F[Cell A1]
    D --> G[Cell B1]
    E --> H[Cell A2]
    E --> I[Cell B2]

    style A fill:#f9f,stroke:#333
    style B fill:#bbf,stroke:#333,color:#fff
    style D fill:#ffd,stroke:#333
    style F fill:#dfd,stroke:#333

每一層都可以通過索引或名稱訪問,例如 workbook.getSheet("銷售明細(xì)") 。這種樹狀結(jié)構(gòu)非常符合人類對(duì)表格的認(rèn)知習(xí)慣,但在大規(guī)模數(shù)據(jù)下卻成了內(nèi)存殺手——每個(gè) Cell 對(duì)象都占用幾十字節(jié),百萬級(jí)單元格輕松吃掉數(shù)百 MB 堆空間。

性能危機(jī):當(dāng) XSSFWorkbook 遇上百萬行數(shù)據(jù)

設(shè)想這樣一個(gè)需求:客戶上傳一份包含 10 萬行商品信息的 Excel 表,要求系統(tǒng)自動(dòng)導(dǎo)入數(shù)據(jù)庫。

新手可能會(huì)這么寫:

XSSFWorkbook wb = new XSSFWorkbook(fileInputStream);
XSSFSheet sheet = wb.getSheetAt(0);

for (Row row : sheet) {
    List<String> values = new ArrayList<>();
    for (Cell cell : row) {
        values.add(formatCellValue(cell));
    }
    saveToDatabase(values); // 批量插入
}

運(yùn)行一次試試?很可能幾秒后 JVM 直接崩潰:

java.lang.OutOfMemoryError: Java heap space

為什么?

因?yàn)? XSSFWorkbook 是基于 DOM 模型的——它會(huì)把整個(gè) .xlsx 文件解析成內(nèi)存中的對(duì)象樹。實(shí)驗(yàn)數(shù)據(jù)顯示,一個(gè) 10MB 的 .xlsx 文件,在加載后可能消耗 超過 500MB 的堆內(nèi)存!原因包括:

  1. XML 解析膨脹 :DOM 解析本身會(huì)產(chǎn)生大量臨時(shí)對(duì)象;
  2. SharedStringsTable 全量緩存 :所有文本字符串一次性加載進(jìn)內(nèi)存;
  3. 樣式表復(fù)制 :每種樣式都被映射為 Java 對(duì)象;
  4. 空單元格占位 :即使為空,也會(huì)創(chuàng)建 XSSFCell 實(shí)例。

這就引出了一個(gè)關(guān)鍵抉擇: UserModel vs EventModel

特性UserModel (  XSSFWorkbook)EventModel (  XSSFReader)
編程模型面向?qū)ο螅庇^易用事件驅(qū)動(dòng),需自定義處理器
內(nèi)存占用高(O(n))極低(O(1))
適用場(chǎng)景小文件讀寫、模板填充大文件流式解析
是否支持修改否(僅讀?。?/td>

要解決大文件問題,我們必須放棄“先把文件裝進(jìn)來再處理”的思維定式,轉(zhuǎn)而采用 SAX 流式解析

流式革命:用 SAX 模式實(shí)現(xiàn)恒定內(nèi)存解析

SAX(Simple API for XML)是一種事件驅(qū)動(dòng)的 XML 解析方式。它不像 DOM 那樣一次性加載全文,而是邊讀邊觸發(fā)回調(diào)函數(shù)。對(duì)于 .xlsx 來說,這意味著我們可以一邊從 ZIP 流中讀取 sheet1.xml ,一邊提取數(shù)據(jù),全程只需幾十 KB 內(nèi)存。

以下是核心實(shí)現(xiàn)步驟:

try (OPCPackage pkg = OPCPackage.open("huge.xlsx")) {
    XSSFReader reader = new XSSFReader(pkg);
    SharedStringsTable sst = reader.getSharedStringsTable();

    XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) reader.getSheetsData();
    while (iter.hasNext()) {
        try (InputStream stream = iter.next()) {
            String sheetName = iter.getSheetName();
            System.out.println("Processing: " + sheetName);

            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
            xmlReader.setContentHandler(new StreamingSheetHandler(sst));
            xmlReader.parse(new InputSource(stream));
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}

其中 StreamingSheetHandler 是我們的自定義處理器:

class StreamingSheetHandler extends DefaultHandler {
    private boolean inCell = false;
    private boolean inValue = false;
    private StringBuilder contents = new StringBuilder();
    private String currentCellRef;

    private final SharedStringsTable sst;

    public StreamingSheetHandler(SharedStringsTable sst) {
        this.sst = sst;
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        if ("c".equals(qName)) {
            currentCellRef = attributes.getValue("r"); // 如 A1, B2
            inCell = true;
        } else if (inCell && "v".equals(qName)) {
            inValue = true;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        if (inValue) {
            contents.append(ch, start, length);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) {
        if ("v".equals(qName)) {
            inValue = false;
            String value = contents.toString().trim();

            // 若單元格類型為 's',表示引用共享字符串表
            String cellType = attributes.getValue("t");
            if ("s".equals(cellType) && !value.isEmpty()) {
                int idx = Integer.parseInt(value);
                value = sst.getItemAt(idx).getString();
            }

            System.out.printf("%s: %s%n", currentCellRef, value);
            contents.setLength(0);
        } else if ("c".equals(qName)) {
            inCell = false;
        }
    }
}

這種方式的優(yōu)勢(shì)顯而易見:

? 內(nèi)存恒定 :不管文件多大,內(nèi)存占用基本不變
? 速度快 :無需構(gòu)建完整對(duì)象樹,解析效率提升 3–5 倍
? 可實(shí)時(shí)輸出 :邊解析邊寫庫,無需等待

不過也有局限:不能隨機(jī)訪問某一行,也不適合做寫操作。如果既要高性能又要可寫能力怎么辦?那就輪到 SXSSF 登場(chǎng)了。

終極方案:SXSSF —— 大規(guī)模導(dǎo)出的秘密武器

如果你需要生成一個(gè)包含百萬行數(shù)據(jù)的 Excel 報(bào)告,傳統(tǒng)的 XSSFWorkbook 會(huì)讓你的服務(wù)器癱瘓。而 SXSSFWorkbook 就是為此類場(chǎng)景量身打造的解決方案。

它是 XSSF 的流式變體,采用“滑動(dòng)窗口”機(jī)制:只將最近 N 行保留在內(nèi)存中,其余溢出至磁盤臨時(shí)文件。當(dāng)最終寫入時(shí),再合并輸出。

// 創(chuàng)建基于 XSSF 的流式工作簿,保留 100 行在內(nèi)存
XSSFWorkbook xwb = new XSSFWorkbook();
SXSSFWorkbook sxwb = new SXSSFWorkbook(xwb, 100);

SXSSFSheet sheet = sxwb.createSheet("數(shù)據(jù)導(dǎo)出");

for (int i = 0; i < 1_000_000; i++) {
    Row row = sheet.createRow(i);
    for (int j = 0; j < 10; j++) {
        Cell cell = row.createCell(j);
        cell.setCellValue("Row" + i + "-Col" + j);
    }
}

try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
    sxwb.write(out);
} finally {
    sxwb.dispose(); // 刪除臨時(shí)文件
}

關(guān)鍵參數(shù)說明:

  • new SXSSFWorkbook(xwb, 100) :前 100 行常駐內(nèi)存,后續(xù)行寫入臨時(shí)文件;
  • sxwb.dispose() :務(wù)必調(diào)用,否則臨時(shí)文件不會(huì)自動(dòng)清理;
  • 支持設(shè)置壓縮、臨時(shí)目錄等高級(jí)選項(xiàng);

注意: poi-3.7.jar 原生不包含 SXSSF(首次出現(xiàn)在 3.8-beta),若無法升級(jí)主版本,可嘗試單獨(dú)引入較新的 poi-ooxml 模塊以獲得支持。

底層支撐:那些你忽略卻至關(guān)重要的輔助依賴包

很多人以為 POI 只是一個(gè) poi.jar ,實(shí)際上它是一套精密協(xié)作的生態(tài)系統(tǒng)。特別是當(dāng)你處理 .xlsx 文件時(shí),以下三個(gè) JAR 包缺一不可:

poi-ooxml-schemas-3.7.jar:XML Schema 的 Java 映射

Open XML 規(guī)范由數(shù)百個(gè) XSD 文件定義,涵蓋 <workbook> 、 <worksheet> 、 <table> 等所有元素。POI 團(tuán)隊(duì)使用 Apache XmlBeans 工具鏈將這些 XSD 預(yù)編譯為 Java 類,打包進(jìn) poi-ooxml-schemas-3.7.jar 。

例如, CTWorkbook.java 對(duì)應(yīng) <workbook> 元素:

public interface CTWorkbook extends XmlObject {
    List<CTSheet> getSheetsList();
    CTSheets addNewSheets();
    boolean isSetSheets();
}

這些類以 CT 開頭(意為 Complex Type),實(shí)現(xiàn)了強(qiáng)類型的 XML 操作。相比原始 DOM,它們提供了更好的 IDE 提示和編譯期檢查。

xmlbeans-2.3.0.jar:運(yùn)行時(shí)綁定引擎

有了生成的類還不夠,還需要一個(gè)運(yùn)行時(shí)引擎來完成 XML ↔ Java 對(duì)象的序列化/反序列化。這就是 xmlbeans 的職責(zé)。

當(dāng)你執(zhí)行:

CTWorksheet ct = sheet.getCTWorksheet();
ct.addNewSheetViews().addNewSheetView().setRightToLeft(true);

XmlBeans 會(huì)在幕后將這些調(diào)用轉(zhuǎn)化為對(duì)應(yīng)的 XML 修改,并最終持久化到 .xlsx 文件中。

然而,這也帶來了潛在風(fēng)險(xiǎn):由于 poi-ooxml-schemas 包體積巨大(約 20MB),且與其他庫可能存在版本沖突(如舊版 Spring Boot 自帶 xmlbeans),很容易引發(fā) LinkageError NoClassDefFoundError 。

推薦排除策略(Maven):

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.17</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.xmlbeans</groupId>
            <artifactId>xmlbeans</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 單獨(dú)引入受控版本 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>3.17</version>
</dependency>
<dependency>
    <groupId>org.apache.xmlbeans</groupId>
    <artifactId>xmlbeans</artifactId>
    <version>2.6.0</version>
</dependency>

這樣可以確保類路徑唯一,避免重復(fù)加載。

dom4j-1.6.1.jar:靈活補(bǔ)充非標(biāo)準(zhǔn)內(nèi)容解析

盡管 POI 主力使用 XmlBeans,但在面對(duì) customXML、VBA 宏或 Visio 圖形元數(shù)據(jù)時(shí),其原生支持有限。此時(shí),輕量級(jí)的 dom4j 成為理想補(bǔ)充。

例如,讀取嵌入式業(yè)務(wù)數(shù)據(jù):

ZipFile zip = new ZipFile("report.xlsx");
ZipEntry entry = zip.getEntry("customXml/item1.xml");

if (entry != null) {
    InputStream is = zip.getInputStream(entry);
    SAXReader reader = new SAXReader();
    Document doc = reader.read(is);

    Element root = doc.getRootElement();
    String bizId = root.elementText("businessId");
    String status = root.element("metadata").attributeValue("status");

    log.info("Found biz context: id={}, status={}", bizId, status);
}

還可以結(jié)合 XPath 實(shí)現(xiàn)復(fù)雜查詢:

Map<String, String> ns = Map.of("x", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
reader.getDocumentFactory().setXPathNamespaceContext(new SimpleNamespaceContext(ns));

List<Node> redCells = doc.selectNodes("http://x:c[x:v/@color='red']");

合理劃分職責(zé)邊界,才能兼顧性能與靈活性:

場(chǎng)景推薦工具
標(biāo)準(zhǔn) Sheet/Cell 操作POI (XSSF/HSSF)
SharedStrings 處理POI + XmlBeans
自定義 XML 數(shù)據(jù)讀取dom4j
圖表結(jié)構(gòu)分析dom4j + XPath
大文件流式解析POI EventModel

生產(chǎn)級(jí)實(shí)踐:Spring Boot 中的安全集成模式

在真實(shí)項(xiàng)目中,我們不僅要考慮功能實(shí)現(xiàn),更要關(guān)注資源管理、異?;謴?fù)和可觀測(cè)性。以下是一個(gè)基于 Spring Boot 的通用 Excel 導(dǎo)入服務(wù)模板:

@Service
@Slf4j
public class ExcelImportService {

    public List<Map<String, Object>> importFromStream(InputStream inputStream, int sheetIndex) {
        List<Map<String, Object>> result = new ArrayList<>();

        try (OPCPackage pkg = OPCPackage.open(inputStream);
             XSSFReader reader = new XSSFReader(pkg)) {

            ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
            XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) reader.getSheetsData();
            int currentIndex = 0;

            while (iter.hasNext()) {
                if (currentIndex++ == sheetIndex) {
                    try (InputStream sheetStream = iter.next()) {
                        parseSheetStream(sheetStream, strings, result);
                        break;
                    }
                } else {
                    iter.next().close(); // 跳過未選中的 sheet
                }
            }

        } catch (NotOfficeXmlFileException e) {
            throw new IllegalArgumentException("無效的 .xlsx 文件", e);
        } catch (OutOfMemoryError e) {
            log.error("內(nèi)存不足,建議啟用流式解析", e);
            throw new RuntimeException("文件過大,無法處理");
        } catch (Exception e) {
            log.error("Excel 解析失敗", e);
            throw new RuntimeException("解析異常:" + e.getMessage(), e);
        }

        return result;
    }

    private void parseSheetStream(InputStream stream, 
                                  ReadOnlySharedStringsTable strings,
                                  List<Map<String, Object>> result) throws Exception {

        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader xr = factory.createXMLStreamReader(stream);

        Map<String, Object> currentRow = null;
        StringBuilder cellValue = new StringBuilder();
        String cellRef = "";
        String cellType = "";

        while (xr.hasNext()) {
            int event = xr.next();

            switch (event) {
                case START_ELEMENT:
                    String tagName = xr.getLocalName();

                    if ("row".equals(tagName)) {
                        currentRow = new LinkedHashMap<>();
                    } else if ("c".equals(tagName)) {
                        cellRef = xr.getAttributeValue(null, "r");
                        cellType = xr.getAttributeValue(null, "t");
                        cellValue.setLength(0);
                    } else if ("v".equals(tagName) || "t".equals(tagName)) {
                        // 準(zhǔn)備接收字符數(shù)據(jù)
                    }
                    break;

                case CHARACTERS:
                    cellValue.append(xr.getText());
                    break;

                case END_ELEMENT:
                    tagName = xr.getLocalName();

                    if ("v".equals(tagName) || "t".equals(tagName)) {
                        String value = cellValue.toString().trim();
                        if (!value.isEmpty() && "s".equals(cellType)) {
                            try {
                                int idx = Integer.parseInt(value);
                                value = strings.getEntryAt(idx).getString();
                            } catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
                        }
                        if (currentRow != null && cellRef.matches("[A-Z]+\\d+")) {
                            currentRow.put(extractColumnName(cellRef), value);
                        }
                    } else if ("row".equals(tagName) && currentRow != null) {
                        result.add(currentRow);
                    }
                    break;
            }
        }
    }

    private String extractColumnName(String ref) {
        return ref.replaceAll("\\d+", "");
    }
}

亮點(diǎn)解析:

自動(dòng)資源管理 try-with-resources 確保 OPCPackage 和流被及時(shí)關(guān)閉
異常分級(jí)處理 :區(qū)分文件格式錯(cuò)誤、內(nèi)存溢出、解析失敗等不同級(jí)別異常
列名提取 :將 A1 , B2 轉(zhuǎn)換為 A , B 作為鍵存儲(chǔ)
跳過無關(guān) sheet :避免加載不需要的工作表,節(jié)省內(nèi)存

配合單元測(cè)試驗(yàn)證邊界情況:

@Test
void should_parse_small_excel_correctly() throws Exception {
    try (InputStream is = getClass().getResourceAsStream("/test-data.xlsx")) {
        List<Map<String, Object>> data = service.importFromStream(is, 0);
        assertThat(data).hasSize(3);
        assertThat(data.get(0)).containsKey("姓名");
    }
}

構(gòu)建工具最佳配置:Maven & Gradle 實(shí)戰(zhàn)指南

最后,別忘了依賴管理的規(guī)范化。強(qiáng)烈建議統(tǒng)一版本,避免混合引入 poi-3.7.jar poi-ooxml-3.5-beta6 這種嚴(yán)重錯(cuò)配的情況。

Maven 推薦配置

<properties>
    <poi.version>3.17</poi.version> <!-- 或 5.2.3 最新版 -->
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-schemas</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
</dependencies>

Gradle 推薦配置

ext {
    poiVersion = '3.17'
}

dependencies {
    implementation "org.apache.poi:poi:${poiVersion}"
    implementation "org.apache.poi:poi-ooxml:${poiVersion}"
    implementation "org.apache.poi:poi-ooxml-schemas:${poiVersion}"
    implementation 'dom4j:dom4j:1.6.1'
}

并通過以下命令檢查依賴樹:

mvn dependency:tree | grep poi

確保所有 POI 模塊來自同一版本系列,杜絕類沖突隱患。

結(jié)語:讓 Excel 自動(dòng)化真正服務(wù)于業(yè)務(wù)

Java POI 不只是一個(gè)工具庫,它是一整套關(guān)于 結(jié)構(gòu)化數(shù)據(jù)流動(dòng) 的設(shè)計(jì)哲學(xué)。從最初的簡單讀寫,到如今的大規(guī)模流式處理,每一次演進(jìn)都在回應(yīng)現(xiàn)實(shí)世界的復(fù)雜挑戰(zhàn)。

掌握它的關(guān)鍵,從來不只是記住幾個(gè) API,而是理解:

  • 什么時(shí)候該用 UserModel,什么時(shí)候必須切換 EventModel?
  • 如何平衡開發(fā)效率與系統(tǒng)穩(wěn)定性?
  • 當(dāng)出現(xiàn) OutOfMemoryError 時(shí),是調(diào)大堆內(nèi)存,還是重構(gòu)解析邏輯?
  • 第三方依賴沖突了,你是盲目排除,還是深入分析類加載機(jī)制?

這才是資深工程師與初級(jí)開發(fā)者之間的真正差距所在。

希望這篇長達(dá)七千字的深度剖析,能幫你建立起一套完整的 POI 認(rèn)知框架。下次當(dāng)你面對(duì)那個(gè)“又大又慢”的 Excel 文件時(shí),心里會(huì)多一份從容與底氣。

畢竟,真正的自動(dòng)化,不是讓機(jī)器干活,而是讓我們掌控機(jī)器的方式變得更聰明。

以上就是Java POI讀取Excel所需全部依賴包的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于Java POI讀取Excel所需依賴包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot2.3集成ELK7.1.0的示例代碼

    SpringBoot2.3集成ELK7.1.0的示例代碼

    這篇文章主要介紹了SpringBoot2.3集成ELK7.1.0的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Java如何對(duì)返回參數(shù)進(jìn)行處理

    Java如何對(duì)返回參數(shù)進(jìn)行處理

    這篇文章主要介紹了Java如何對(duì)返回參數(shù)進(jìn)行處理問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • IntelliJ IDEA 使用經(jīng)驗(yàn)總結(jié)(推薦)

    IntelliJ IDEA 使用經(jīng)驗(yàn)總結(jié)(推薦)

    這篇文章主要介紹了IntelliJ IDEA 使用經(jīng)驗(yàn)總結(jié),非常不錯(cuò),具有參考價(jià)值,需要的朋友可以參考下
    2018-02-02
  • Spring?Security使用數(shù)據(jù)庫登錄認(rèn)證授權(quán)

    Spring?Security使用數(shù)據(jù)庫登錄認(rèn)證授權(quán)

    本文主要介紹了Spring?Security使用數(shù)據(jù)庫登錄認(rèn)證授權(quán),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • JAVA面試題 從源碼角度分析StringBuffer和StringBuilder的區(qū)別

    JAVA面試題 從源碼角度分析StringBuffer和StringBuilder的區(qū)別

    這篇文章主要介紹了JAVA面試題 從源碼角度分析StringBuffer和StringBuilder的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面我們來一起學(xué)習(xí)下吧
    2019-07-07
  • SpringBoot?將配置文件掛到?jar?包外面的操作方法

    SpringBoot?將配置文件掛到?jar?包外面的操作方法

    在 SpringBoot 中,可以將配置文件放在 jar 包外面,這樣可以方便地修改配置而不需要重新打包和部署,這篇文章主要介紹了SpringBoot?如何將配置文件掛到?jar?包外面,需要的朋友可以參考下
    2023-03-03
  • Java時(shí)間戳類Instant的使用詳解

    Java時(shí)間戳類Instant的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Java中時(shí)間戳類Instant的使用方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下
    2022-09-09
  • SpringCloud之動(dòng)態(tài)刷新、重試、服務(wù)化的實(shí)現(xiàn)

    SpringCloud之動(dòng)態(tài)刷新、重試、服務(wù)化的實(shí)現(xiàn)

    這篇文章主要介紹了SpringCloud 之動(dòng)態(tài)刷新、重試、服務(wù)化的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Java設(shè)計(jì)模式之策略模式(Strategy模式)介紹

    Java設(shè)計(jì)模式之策略模式(Strategy模式)介紹

    這篇文章主要介紹了Java設(shè)計(jì)模式之策略模式(Strategy模式)介紹,Strategy是屬于設(shè)計(jì)模式中對(duì)象行為型模式,要是定義一系列的算法,這些算法一個(gè)個(gè)封裝成單獨(dú)的類,需要的朋友可以參考下
    2015-03-03
  • 如何提高java代碼的重用性

    如何提高java代碼的重用性

    在本篇文章中小編給各位分享了關(guān)于如何提高java代碼的重用性的相關(guān)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們參考下。
    2019-07-07

最新評(píng)論

新田县| 新余市| 延津县| 岐山县| 格尔木市| 伊金霍洛旗| 成武县| 明溪县| 福安市| 庆阳市| 肃北| 娄底市| 沙河市| 尚志市| 哈尔滨市| 花莲县| 锡林郭勒盟| 庄浪县| 芦山县| 崇阳县| 莱芜市| 无棣县| 金山区| 平山县| 泰顺县| 东港市| 察雅县| 龙井市| 北安市| 新化县| 宁夏| 营山县| 彰武县| 襄汾县| 霍邱县| 澄城县| 淄博市| 河西区| 大荔县| 前郭尔| 宣武区|