53

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

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

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

そのようなファイルサイズ(9MB)で機能するように見えますが、私にとっては、使用可能なすべてのシステムメモリを割り当てても機能しないだけです。(まあ、ファイルが15倍以上大きいことを考えると当然です)

XSSFの基盤となるXMLに基づく(入る)処理を行わずに、すべてのメモリを消費しない方法でブックをロードする方法があるかどうかを知りたいです。(言い換えれば、ピューリタンPOIソリューションを維持する)

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

4

8 に答える 8

73

私はWebサーバー環境でも同様の状況にありました。アップロードの一般的なサイズは約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シートの構造上、ストリームの現在のウィンドウですべてのデータを使用できるわけではありません。ただし、セルから単純なデータを読み出そうとしているだけの場合は、そのために非常にうまく機能します。

于 2015-02-08T17:58:16.320 に答える
17

ストリームの代わりにファイルを使用することで、メモリ使用量を改善できます。(ストリーミング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

ファイルとInputStreams

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

于 2013-07-09T07:38:04.263 に答える
10

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に相当するものはなく、多くのレコードで前後のバイトオフセットとインデックスオフセットがあるため、実行するのはかなり困難です...

特定のケースでは、明確なコメントで説明されているように、XSSFEventModelコードを使用することをお勧めします。開始するにはPOIのドキュメントを参照してから、POIとTikaのこれら 3つの クラスを見て、詳細を確認してください。

于 2012-11-12T19:37:02.130 に答える
8

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

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

于 2012-08-09T22:12:06.877 に答える
6

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

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

つまり、org.xml.sax.helpers.DefaultHandlerXLSXfilezの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;
    }
}

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

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;
}
于 2017-07-07T10:54:45.843 に答える
0

poiから調査したmonitorjblの回答とテストスイートに基づいて、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);
            }
        }
    }
}
于 2018-08-14T17:13:49.623 に答える
0

最新のコードについては、これを使用してください


InputStream file = new FileInputStream(
                    new File("uploads/" + request.getSession().getAttribute("username") + "/" + userFile));
Workbook workbook = StreamingReader.builder().rowCacheSize(100) // number of rows to keep in memory
                    .bufferSize(4096) // index of sheet to use (defaults to 0)
                    .open(file); // InputStream or File for XLSX file (required)

Iterator<Row> rowIterator = workbook.getSheetAt(0).rowIterator();
 while (rowIterator.hasNext()) {
     while (cellIterator.hasNext()) {
         Cell cell = cellIterator.next();
        String cellValue = dataFormatter.formatCellValue(cell);
     }}

于 2020-01-07T12:53:57.940 に答える
-1

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

于 2013-01-19T19:57:39.710 に答える