簡體   English   中英

帶有JXLS 2的SXSSF變壓器

[英]SXSSF Transformer with JXLS 2

我想將SXSSF變壓器與JXLS一起使用。 我試圖以這樣的方式編寫模板:不會出現“嘗試在已寫入磁盤的范圍內寫一行”的異常。 模板捕獲已知列(例如“ HEADER 0”)和未知列(以“ _dynamic”結尾的列)。 動態列的數量可能因運行而異。

如果將SXSSF窗口配置為大於行數,則沒有問題。 如果將窗口設置為少於行數,則會出現“試圖寫一行...”的異常。 但是,在兩種情況下都將創建工作簿。 在具有足夠的窗口大小的情況下,結果中將包含已知列(“ HEADER 0”)。 如果窗口大小不足,則結果中將包含已知的列值(盡管有例外),但實際的列文本(仍為“ HEADER 0”)將丟失。

我想采用這樣的方法,因為行數可以達到100,000,並且我想根據需要將數據刷新到磁盤。

在JXLS中甚至可以做這樣的事情嗎? 如果是這樣,是否有辦法更改模板而不必編寫任何有關數據的Java代碼?

這是代碼:

public class JxlsTest {

@Test
public void sxssfDynamicColumns() throws Exception {
    List<Map<String, Object>> lotsOfStuff = createLotsOfStuff();

    Context context = new PoiContext();
    context.putVar("lotsOStuff", lotsOfStuff);
    context.putVar("columns", new Columns());

    try (InputStream in = getClass().getClassLoader().getResourceAsStream("stuff_sxssf_template.xlsx")) {
        try (OutputStream os = new FileOutputStream("stuff_sxssf_out.xlsx")) {
            Workbook workbook = WorkbookFactory.create(in);
            PoiTransformer transformer = PoiTransformer.createSxssfTransformer(workbook, 5, false);

            AreaBuilder areaBuilder = new XlsCommentAreaBuilder(transformer);
            List<Area> xlsAreaList = areaBuilder.build();
            Area xlsArea = xlsAreaList.get(0);
            xlsArea.applyAt(new CellRef("Result!A1"), context);
            SXSSFWorkbook workbook2 = (SXSSFWorkbook) transformer.getWorkbook();
            workbook2.write(os);
        }
    }
}

private List<Map<String, Object>> createLotsOfStuff() {
    Map<String, Object> stuff1 = new LinkedHashMap<>();
    Map<String, Object> stuff2 = new LinkedHashMap<>();

    stuff1.put("header0", "stuff_1_value0");
    stuff1.put("header1_dynamic", "stuff_1_value1");
    stuff1.put("header2_dynamic", "stuff_1_value2");
    stuff1.put("header3_dynamic", "stuff_1_value3");

    stuff2.put("header0", "stuff_2_value0");
    stuff2.put("header1_dynamic", "stuff_2_value1");
    stuff2.put("header2_dynamic", "stuff_2_value2");
    stuff2.put("header3_dynamic", "stuff_2_value3");

    return Arrays.asList(stuff1, stuff2);
}

}

和支持的“列”實用程序:

public class Columns {

public Collection<String> keyOf(List<Map<String, Object>> row) {
    return row.get(0).keySet().stream().filter(k -> k.endsWith("_dynamic")).collect(Collectors.toList());
}

public Collection<Object> valueOf(Map<String, Object> row) {
    return row.entrySet().stream()
            .filter(entry -> entry.getKey() != null && entry.getKey().endsWith("_dynamic"))
            .map(Entry::getValue)
            .collect(Collectors.toList());
}

}

和模板: 在此處輸入圖片說明

具有足夠的SXSSF窗口的輸出(注意HEADER 0出現):

在此處輸入圖片說明

SXSSF窗口不足的輸出(注意HEADER 0不會出現):

在此處輸入圖片說明

來自SXXF窗口不足的錯誤:

18:33:20.653 [main] DEBUG org.jxls.area.XlsArea - Applying XlsArea at Result!A1
18:33:20.693 [main] ERROR org.jxls.area.XlsArea - Failed to transform Template!B1 into Result!B1
java.lang.IllegalArgumentException: Attempting to write a row[0] in the range [0,0] that is already written to disk.
    at org.apache.poi.xssf.streaming.SXSSFSheet.createRow(SXSSFSheet.java:115) ~[poi-ooxml-3.12.jar:3.12]
    at org.jxls.transform.poi.PoiTransformer.transform(PoiTransformer.java:112) ~[jxls-poi-1.0.8.jar:na]
    at org.jxls.area.XlsArea.transformTopStaticArea(XlsArea.java:232) [jxls-2.2.9.jar:na]
    at org.jxls.area.XlsArea.applyAt(XlsArea.java:134) [jxls-2.2.9.jar:na]

更新我發現,如果刪除動態標頭(請參見屏幕快照中B4單元中的模板),則不會引發異常,並且一切正常。 因此,首先評估與行相關的模板,然后JXLS回來評估動態標頭模板。 有沒有辦法讓JXLS首先評估標頭模板?

問題是Jxls分兩個階段處理靜態單元。

第一階段發生在應用命令之前,並且僅處理頂部命令行之前的單元。

處理完所有命令后,將觸發第二階段,並嘗試處理所有剩余的靜態單元格。

第二階段不適用於SXSSF工作簿,因為它無法更新位於命令處理期間寫入的單元之前的靜態單元。

現在有一個針對Jxls master分支的修復程序 ,如果使用了流式轉換器,則應該通過對靜態單元進行不同的處理來解決此問題。 另請參閱相關的Jxls問題#160

該修復程序應在即將發布的Jxls 2.7.0中發布。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM