Comment charger un grand fichier xlsx avec Apache POI?

j'ai un grand .fichier xlsx (141 Mo, contenant 293413 lignes avec 62 colonnes chacune) je dois effectuer certaines opérations à l'intérieur.

j'ai des problèmes avec le chargement de ce fichier ( OutOfMemoryError ), POI ayant une grande empreinte mémoire sur xssf (xlsx) workbooks.

cette question de SO est similaire, et la solution présentée est d'augmenter la mémoire allouée/maximale de la VM.

il semble fonctionner pour ce genre de taille de fichier (9MB), mais pour moi, il ne fonctionne tout simplement pas même si une allouer toute la mémoire système disponible. (Eh bien, ce n'est pas surprenant vu que le fichier est plus de 15 fois plus grand)

j'aimerais savoir s'il y a un moyen de charger le classeur d'une manière qui ne consommera pas toute la mémoire, et pourtant, sans faire le traitement basé (aller dans) le XML sous-jacent de XSSF. (En d'autres termes, maintenir une solution de POI puritan)

S'il n'y a pas difficile, vous êtes les bienvenus pour le dire ("il n'y a pas.") et montrez-moi comment trouver une solution "XML".

30
demandé sur Community 2012-08-10 00:58:35

7 réponses

j'étais dans une situation similaire avec un environnement webserver. La taille typique des téléchargements ont été ~150k lignes et il n'aurait pas été bon de consommer une tonne de mémoire à partir d'une seule demande. L'API de Streaming Apache POI fonctionne bien pour cela, mais elle nécessite une refonte totale de votre logique de lecture. J'ai déjà eu un tas de logique de lecture en utilisant l'API standard que je ne voulais pas avoir à refaire, donc j'ai écrit ceci à la place: https://github.com/monitorjbl/excel-streaming-reader

ce n'est pas tout à fait un remplacement direct pour la classe standard XSSFWorkbook , mais si vous êtes juste itérer à travers les lignes il se comporte de la même manière:

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

il y a quelques réserves à l'utiliser; en raison de la façon dont les feuilles XLSX sont structurées, toutes les données ne sont pas disponibles dans la fenêtre courante du flux. Cependant, si vous êtes juste en train d'essayer de lire des données simples à partir des cellules, il fonctionne assez bien pour cela.

42
répondu monitorjbl 2015-02-08 17:58:16

une amélioration de l'utilisation de la mémoire peut être faite en utilisant un fichier au lieu d'un flux. (Il est préférable d'utiliser une API de streaming, mais les API de Streaming ont des limites, voir http://poi.apache.org/spreadsheet/index.html )

donc au lieu de

Workbook workbook = WorkbookFactory.create(inputStream);

faire

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

c'est selon: http://poi.apache.org/spreadsheet/quick-guide.html#FileInputStream

Files vs InputStreams

" lors de l'ouverture d'un classeur, A.xls HSSFWorkbook, ou un .xlsx XSSFWorkbook, le classeur peut être chargé à partir d'un fichier ou D'une entrée. L'utilisation d'un objet File permet de réduire la consommation de mémoire, tandis qu'une entrée nécessite plus de mémoire puisqu'elle doit amortir l'ensemble du fichier."

11
répondu rjdkolb 2013-07-09 07:38:04

le support Excel dans Apache POI, HSSF et XSSF, prend en charge 3 modes différents.

On est complet, dans les DOM-Comme en mémoire "UserModel", qui prend en charge à la fois la lecture et de l'écriture. En utilisant les interfaces communes SS (tableur), vous pouvez coder pour les deux HSSF (.xls) et XSSF (.xlsx) essentiellement transparente. Cependant, il a besoin de beaucoup de mémoire.

POI prend également en charge un mode de lecture en continu pour traiter les fichiers, le modèle D'événement. Ce qui est beaucoup plus faible niveau que le modèle UserModel, et vous obtient très près du format de fichier. Pour HSSF (.xls) vous obtenez un flux d'enregistrements, et optionnellement de l'aide pour les manipuler (cellules manquantes, suivi de format, etc.). Pour XSSF (.xlsx) vous obtenez des flux D'événements SAX À partir des différentes parties du fichier, avec de l'aide pour obtenir la bonne partie du fichier et aussi le traitement facile des parties communes mais petites du fichier.

pour XSSF (.xlsx) seulement, POI prend également en charge un streaming en écriture uniquement, adapté aux mais peu de mémoire écrit. Il supporte en grande partie de nouveaux fichiers (certains types d'ajout sont possibles). Il n'y a pas d'équivalent HSSF, et en raison des décalages de byte et d'index dans de nombreux enregistrements, ce serait assez difficile à faire...

pour votre cas spécifique, comme décrit dans vos commentaires explicatifs, je pense que vous voudrez utiliser le code Xssf EventModel. Voir la PVE de la documentation pour commencer, puis essayez de regarder ces trois classes POI et Tika qui l'utilisent pour plus de détails.

7
répondu Gagravarr 2012-11-12 19:37:02
La PVE

comprend maintenant une IPA pour ces cas. SXSSF http://poi.apache.org/spreadsheet/index.html Il ne charge pas tout sur la mémoire de sorte qu'il pourrait vous permettre de gérer un tel fichier.

Note: j'ai lu que SXSSF fonctionne comme une API d'écriture. Le chargement doit être fait en utilisant XSSF sans entrer le fichier (pour éviter une pleine charge en mémoire)

5
répondu Alfabravo 2015-12-17 22:02:25

vérifiez ce post. Je montre comment utiliser Sax parser pour traiter un fichier XLSX.

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

en bref, j'ai étendu org.xml.sax.helpers.DefaultHandler qui traite la structure XML pour les fichiers XLSX. t est l'événement par l'analyseur 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;
    }
}

et ensuite je passe le fichier XLSX de présentation 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;
}
3
répondu Yan Khonski 2018-03-16 12:26:19

vous pouvez utiliser SXXSF au lieu d'utiliser HSSF. Je pourrais générer excel avec 200000 lignes.

0
répondu user1993509 2013-01-19 20:03:07

basé sur la réponse de monitorjbl et la suite de test explorée à partir de poi, a travaillé pour moi sur le fichier XLSX multi-feuilles avec 200K enregistrements (taille > 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
répondu ak2205 2018-08-14 17:13:49