web-dev-qa-db-ja.com

Apache POIで大きなxlsxファイルをロードする方法は?

大きな.xlsxファイル(141 MB、それぞれ62列の293413行が含まれています)内でいくつかの操作を実行する必要があります。

POIにはXSSF(xlsx)ブックのメモリフットプリントが大きいため、このファイル(OutOfMemoryError)の読み込みに問題があります。

このSO質問 は同様であり、提示された解決策は、VMの割り当て/最大メモリを増やすことです。

それはその種のファイルサイズ(9MB)で動作するようですが、私にとっては、利用可能なシステムメモリをすべて割り当てても動作しません。 (まあ、ファイルのサイズが15倍以上であることを考えると驚くことではありません)

すべてのメモリを消費しない方法でワークブックをロードする方法がありますが、XSSFの基礎となるXMLに基づいて処理を行うことはありませんか? (言い換えれば、ピューリタンPOIソリューションの維持)

難しいことがなければ、それを言って(「ありません」)、「XML」ソリューションへの道を教えてください。

39
XenoRo

ウェブサーバー環境でも同様の状況にありました。アップロードの一般的なサイズは〜15万行で、1回のリクエストで大量のメモリを消費するのは良くありませんでした。 Apache POI Streaming APIはこれに適していますが、読み取りロジックを全面的に再設計する必要があります。やり直す必要のない標準APIを使用した一連の読み取りロジックが既にあったため、代わりにこれを作成しました。 https://github.com/monitorjbl/Excel-streaming-reader =

これは完全に標準のXSSFWorkbookクラスのドロップイン置換ではありませんが、行を反復するだけの場合は同様に動作します:

import com.monitorjbl.xlsx.StreamingReader;

InputStream is = new FileInputStream(new File("/path/to/workbook.xlsx"));
StreamingReader reader = StreamingReader.builder()
        .rowCacheSize(100)    // number of rows to keep in memory (defaults to 10)
        .bufferSize(4096)     // buffer size to use when reading InputStream to file (defaults to 1024)
        .sheetIndex(0)        // index of sheet to use (defaults to 0)
        .read(is);            // InputStream or File for XLSX file (required)

for (Row r : reader) {
  for (Cell c : r) {
    System.out.println(c.getStringCellValue());
  }
}     

使用するにはいくつかの注意事項があります。 XLSXシートの構造により、ストリームの現在のウィンドウですべてのデータが利用できるわけではありません。ただし、セルから単純なデータを読み取ろうとしているだけであれば、それは非常にうまく機能します。

49
monitorjbl

ストリームの代わりにファイルを使用することで、メモリ使用量を改善できます。 (ストリーミングAPIを使用することをお勧めしますが、ストリーミングAPIには制限があります。 http://poi.Apache.org/spreadsheet/index.html を参照してください)

代わりに

Workbook workbook = WorkbookFactory.create(inputStream);

行う

Workbook workbook = WorkbookFactory.create(new File("yourfile.xlsx"));

これは次のとおりです: http://poi.Apache.org/spreadsheet/quick-guide.html#FileInputStream

Files vs InputStreams

「.xls HSSFWorkbookまたは.xlsx XSSFWorkbookのいずれかでワークブックを開くと、ワークブックはFileまたはInputStreamからロードできます。Fileオブジェクトを使用すると、メモリの消費が少なくなりますが、InputStreamはより多くのメモリを必要とします。ファイル全体をバッファリングします。」

11
rjdkolb

Apache POI、HSSF、およびXSSFのExcelサポートは、3つの異なるモードをサポートしています。

1つは、読み取りと書き込みの両方をサポートする完全なDOMのようなメモリ内「UserModel」です。共通のSS(SpreadSheet)インターフェイスを使用すると、HSSF(.xls)とXSSF(.xlsx)の両方を基本的に透過的にコーディングできます。ただし、大量のメモリが必要です。

POIは、ファイルを処理するストリーミング読み取り専用の方法、EventModelもサポートしています。これはUserModelよりもはるかに低レベルであり、ファイル形式に非常に近くなります。 HSSF(.xls)の場合、レコードのストリームを取得し、オプションでそれらの処理(セルの欠落、フォーマットの追跡など)のいくつかのヘルプを取得します。 XSSF(.xlsx)の場合、ファイルのさまざまな部分からSAXイベントのストリームを取得します。ファイルの適切な部分を取得するのに役立ち、ファイルの一般的で小さなビットを簡単に処理できます。

XSSF(.xlsx)の場合のみ、POIは書き込み専用のストリーミング書き込みもサポートします。これは、低レベルですがメモリの少ない書き込みに適しています。ただし、主に新しいファイルのみをサポートします(特定の種類の追加が可能です)。 HSSFに相当するものはありません。また、多くのレコードには前後のバイトオフセットとインデックスオフセットがあるため、実行するのはかなり困難です。

明確なコメントで説明されているように、特定のケースでは、XSSF EventModelコードを使用する必要があると思います。 POIドキュメント を参照して開始してから、 thesethreeclasses を使用してそれを使用する詳細については。

9
Gagravarr

POIには、これらのケースのためのAPIが含まれるようになりました。 SXSSF http://poi.Apache.org/spreadsheet/index.html メモリ上のすべてをロードするわけではないため、このようなファイルを処理できます。

注:SXSSFは書き込みAPIとして機能することを読みました。ロードは、ファイルを入力ストリーム化せずにXSSFを使用して実行する必要があります(メモリへの完全なロードを回避するため)

7
Alfabravo

この投稿を確認してください。 SAXパーサーを使用してXLSXファイルを処理する方法を示します。

https://stackoverflow.com/a/44969009/4587961

つまり、org.xml.sax.helpers.DefaultHandlerは、XLSX filezのXML構造を処理します。 tはイベントパーサー-SAXです。

class SheetHandler extends DefaultHandler {

    private static final String ROW_EVENT = "row";
    private static final String CELL_EVENT = "c";

    private SharedStringsTable sst;
    private String lastContents;
    private boolean nextIsString;

    private List<String> cellCache = new LinkedList<>();
    private List<String[]> rowCache = new LinkedList<>();

    private SheetHandler(SharedStringsTable sst) {
        this.sst = sst;
    }

    public void startElement(String uri, String localName, String name,
                             Attributes attributes) throws SAXException {
        // c => cell
        if (CELL_EVENT.equals(name)) {
            String cellType = attributes.getValue("t");
            if(cellType != null && cellType.equals("s")) {
                nextIsString = true;
            } else {
                nextIsString = false;
            }
        } else if (ROW_EVENT.equals(name)) {
            if (!cellCache.isEmpty()) {
                rowCache.add(cellCache.toArray(new String[cellCache.size()]));
            }
            cellCache.clear();
        }

        // Clear contents cache
        lastContents = "";
    }

    public void endElement(String uri, String localName, String name)
            throws SAXException {
        // Process the last contents as required.
        // Do now, as characters() may be called more than once
        if(nextIsString) {
            int idx = Integer.parseInt(lastContents);
            lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
            nextIsString = false;
        }

        // v => contents of a cell
        // Output after we've seen the string contents
        if(name.equals("v")) {
            cellCache.add(lastContents);
        }
    }

    public void characters(char[] ch, int start, int length)
            throws SAXException {
        lastContents += new String(ch, start, length);
    }

    public List<String[]> getRowCache() {
        return rowCache;
    }
}

そして、XMLを送信するXLSXファイルを解析します

private List<String []> processFirstSheet(String filename) throws Exception {
    OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
    XSSFReader r = new XSSFReader(pkg);
    SharedStringsTable sst = r.getSharedStringsTable();

    SheetHandler handler = new SheetHandler(sst);
    XMLReader parser = fetchSheetParser(handler);
    Iterator<InputStream> sheetIterator = r.getSheetsData();

    if (!sheetIterator.hasNext()) {
        return Collections.emptyList();
    }

    InputStream sheetInputStream = sheetIterator.next();
    BufferedInputStream bisSheet = new BufferedInputStream(sheetInputStream);
    InputSource sheetSource = new InputSource(bisSheet);
    parser.parse(sheetSource);
    List<String []> res = handler.getRowCache();
    bisSheet.close();
    return res;
}

public XMLReader fetchSheetParser(ContentHandler handler) throws SAXException {
    XMLReader parser = new SAXParser();
    parser.setContentHandler(handler);
    return parser;
}
3
Yan Khonski

HSSFを使用する代わりにSXXSFを使用できます。 200000行のExcelを生成できました。

0
user1993509

Monitorjblの回答とpoiから調査されたテストスイートに基づいて、以下は200Kレコード(サイズ> 50 MB)のマルチシートxlsxファイルで私のために働いた:

import com.monitorjbl.xlsx.StreamingReader;
. . .
try (
        InputStream is = new FileInputStream(new File("sample.xlsx"));
        Workbook workbook = StreamingReader.builder().open(is);
) {
    DataFormatter dataFormatter = new DataFormatter();
    for (Sheet sheet : workbook) {
        System.out.println("Processing sheet: " + sheet.getSheetName());
        for (Row row : sheet) {
            for (Cell cell : row) {
                String value = dataFormatter.formatCellValue(cell);
            }
        }
    }
}
0
ak2205