it-swarm.com.de

Wie lade ich eine große xlsx-Datei mit Apache POI?

Ich habe eine große XLSX-Datei (141 MB, die 293413 Zeilen mit jeweils 62 Spalten enthält). Ich muss einige Vorgänge darin ausführen.

Ich habe Probleme beim Laden dieser Datei (OutOfMemoryError), da POI einen großen Speicherbedarf in XSSF-Arbeitsmappen (xlsx) hat.

Diese SO - Frage ist ähnlich, und die vorgestellte Lösung besteht darin, den zugewiesenen/maximalen Speicher der VM zu erhöhen.

Es scheint für diese Art von Dateigröße (9 MB) zu funktionieren, aber für mich funktioniert es einfach nicht, selbst wenn alle verfügbaren Systemspeicher zugewiesen werden. (Nun, es ist keine Überraschung, wenn man bedenkt, dass die Datei über 15 Mal größer ist.)

Ich möchte wissen, ob es eine Möglichkeit gibt, die Arbeitsmappe so zu laden, dass nicht der gesamte Arbeitsspeicher verbraucht wird, und dennoch ohne die Verarbeitung auf Basis der XSSF-XML durchzuführen. (Mit anderen Worten, eine puritan POI-Lösung beibehalten)

Wenn es nicht schwierig ist, können Sie es gerne sagen ("Keine".) Und mir den Weg zu einer "XML" -Lösung zeigen.

33
XenoRo

Ich befand mich in einer ähnlichen Situation mit einer Webserver-Umgebung. Die typische Größe der Uploads betrug ~ 150.000 Zeilen und es wäre nicht gut gewesen, eine Menge Speicher für eine einzige Anforderung zu verbrauchen. Die Apache-POI-Streaming-API funktioniert gut, erfordert jedoch eine vollständige Neugestaltung Ihrer Leselogik. Ich hatte bereits eine Reihe von Leselogik mit der Standard-API, die ich nicht wiederholen wollte, und schrieb stattdessen Folgendes: https://github.com/monitorjbl/Excel-streaming-reader

Es ist nicht vollständig ein Ersatz für die Standardklasse XSSFWorkbook, aber wenn Sie nur durch Zeilen iterieren, verhält es sich ähnlich:

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());
  }
}     

Es gibt einige Einschränkungen bei der Verwendung. Aufgrund der Strukturierung von XLSX-Tabellen sind nicht alle Daten im aktuellen Fenster des Streams verfügbar. Wenn Sie jedoch nur versuchen, einfache Daten aus den Zellen auszulesen, funktioniert das ziemlich gut.

45
monitorjbl

Eine Verbesserung der Speicherauslastung kann durch die Verwendung einer Datei anstelle eines Streams erzielt werden. (Es ist besser, eine Streaming-API zu verwenden, aber die Streaming-APIs haben Einschränkungen, siehe http://poi.Apache.org/ Tabelle/index.html )

Also statt

Workbook workbook = WorkbookFactory.create(inputStream);

tun

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

Dies ist laut: http://poi.Apache.org/spreadsheet/quick-guide.html#FileInputStream

Dateien vs InputStreams

"Beim Öffnen einer Arbeitsmappe, entweder eines .xls HSSFWorkbook oder eines .xlsx XSSFWorkbook, kann die Arbeitsmappe entweder aus einer Datei oder einem InputStream geladen werden. Die Verwendung eines File-Objekts ermöglicht einen geringeren Speicherbedarf, während ein InputStream mehr Speicher benötigt als er die gesamte Datei zwischenspeichern. "

11
rjdkolb

Die Excel-Unterstützung in Apache POI, HSSF und XSSF unterstützt 3 verschiedene Modi. 

Eines ist ein vollständiges, DOM-ähnliches In-Memory "UserModel", das sowohl Lesen als auch Schreiben unterstützt. Mit den üblichen SS (SpreadSheet) -Schnittstellen können Sie für HSSF (.xls) und XSSF (.xlsx) grundsätzlich transparent codieren. Es benötigt jedoch viel Speicher.

POI unterstützt auch eine schreibgeschützte Streaming-Methode, das EventModel. Dies ist viel niedriger als das UserModel und bringt Sie dem Dateiformat sehr nahe. Für HSSF (.xls) erhalten Sie einen Stream von Datensätzen und optional einige Hilfestellungen beim Umgang mit ihnen (fehlende Zellen, Formatverfolgung usw.). Für XSSF (.xlsx) erhalten Sie Streams von SAX-Ereignissen aus den verschiedenen Teilen der Datei, mit der Hilfe, um den richtigen Teil der Datei zu erhalten, und auch einfache Verarbeitung allgemeiner, aber kleiner Bits der Datei.

Nur für XSSF (.xlsx) unterstützt POI auch schreibgeschütztes Streaming-Schreiben, das sich für das Schreiben auf niedrigem Niveau eignet, das jedoch nur wenig Speicherplatz benötigt. Es unterstützt jedoch größtenteils nur neue Dateien (bestimmte Anhänge sind möglich). Es gibt kein HSSF-Äquivalent, und aufgrund von Byte-Offsets und Index-Offsets in vielen Datensätzen wäre das ziemlich schwierig.

Für Ihren speziellen Fall, wie in Ihren erläuternden Kommentaren beschrieben, sollten Sie den XSSF-EventModel-Code verwenden. Sehen Sie die POI-Dokumentation , um zu beginnen. Versuchen Sie dann, diesedreiKlassen in POI und Tika zu suchen, die weitere Details enthalten.

9
Gagravarr

POI enthält jetzt eine API für diese Fälle. SXSSF http://poi.Apache.org/spreadsheet/index.html .__ Es lädt nicht den gesamten Arbeitsspeicher, sodass Sie möglicherweise mit einer solchen Datei umgehen können.

Hinweis: Ich habe gelesen, dass SXSSF als Schreib-API arbeitet. Das Laden sollte mit XSSF ohne Eingabe der Datei erfolgen (um ein vollständiges Laden der Datei zu vermeiden)

6
Alfabravo

Überprüfen Sie diesen Beitrag. Ich zeige, wie man mit dem SAX-Parser eine XLSX-Datei verarbeitet.

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

Kurz gesagt, habe ich org.xml.sax.helpers.DefaultHandler um XML-Struktur für XLSX-Dateien erweitert. t ist Ereignis-Parser - 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;
    }
}

Und dann parse ich die XML-Presending-XLSX-Datei

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

Sie können SXXSF anstelle von HSSF verwenden. Ich könnte Excel mit 200000 Zeilen erzeugen.

0
user1993509

Basierend auf der Antwort- und Testsuite von monitorjbl, die in poi untersucht wurde, arbeitete die folgende Arbeit für mich an einer Multi-Sheet-XLSX-Datei mit 200.000 Datensätzen (Größe> 50 MB):

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