[JAVA] Ich möchte OutOfMemory bei der Ausgabe großer Dateien mit POI vermeiden

Es gibt einen Prozess zum Ausgeben einer Excel-Datei mithilfe des POI. Es ist eine Spezifikation, die bis zu 10000 Datenzeilen in 48 Spalten ausgibt. Als ich jedoch versuchte, 10000 Zeilen auszugeben, trat ein OutOfMemory-Fehler auf. Notieren Sie sich, wie Sie mit solchen Fällen umgehen sollen.

Sagen Sie nicht, dass Java überhaupt nicht so viele Daten in Excel verarbeitet. ..

Umsetzung des Problems

Das Bild der Quelle, bei der das Problem aufgetreten ist, lautet wie folgt.

Problemquelle


	File file = new File(tempPath);	//Temporärer Dateipfad Vorab eine Datei erstellt Verwenden Sie diese als Vorlage
	XSSFWorkbook workbook = (XSSFWorkbook) WorkbookFactory.create(file);
	XSSFSheet sheet = workbook.getSheetAt(0);
	XSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Referenzzeile: Kopieren Sie den Stil jeder Zelle
	int rowCnt = 1;
	for (1 erfasste Daten: list) {	//Es wird davon ausgegangen, dass die Liste im Voraus erhalten wurde und die Quelle nicht angegeben ist.
		sheet.createRow(sheet.getLastRowNum() + 1);
		XSSFRow newRow = sheet.getRow(sheet.getLastRowNum());
		int cellCnt = 0;
		XSSFCell originCell = null;
		XSSFCell newCell = null;
		XSSFCellStyle style = workbook.createCellStyle();
		originCell = baseRow.getCell(cellCnt);
		newCell = newRow.createCell(cellCnt++);
		//Zellenstil kopieren
		style.cloneStyleFrom(originCell.getCellStyle());
		newCell.setCellStyle(style);
		//Kopie des Zelltyps
		newCell.setCellType(originCell.getCellType());
		newCell.setCellValue(1 erfasste Daten.Wert);
		//Wiederholen Sie die Einstellung für 14 Spalten
	}
	//Erstellungsdatum festlegen
	sheet.getRow(0).getCell(2).setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
	//Basislinie löschen
	sheet.shiftRows(baseRow.getRowNum() + 1, sheet.getLastRowNum(), -1);
	//Schreiben Sie in den Ausgabestream und erhalten Sie das Byte-Array
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	workbook.write(baos);
	excelData = baos.toByteArray();
	baos.close();
	workbook.close();
	//Temporäre Datei löschen
	file.delete();
	//Fügen Sie das Byte-Array in die Antwort ein und laden Sie es herunter

Der Umriss des Prozesses ist wie folgt.

Beim Aufrufen von XSSFWorkbook.write () ist ein OutOfMemory-Fehler aufgetreten.

Gegenmaßnahmen

Wenn ich die Gegenmaßnahmenmethode gegoogelt habe, wird gesagt, dass die Verarbeitung einfacher ist, wenn SXSSF Workbook anstelle von XSSF Workbook verwendet wird. SXSSFWorkbook Ich habe auf die folgenden Dokumente verwiesen.

https://poi.apache.org/spreadsheet/#SXSSF+%28Since+POI+3.8+beta3%29

Es ist eine Erweiterung von XSSF. XSSF kann auf alle Zeilen zugreifen und alle API-Funktionen können verwendet werden. Da jedoch alle Zeileninformationen im Speicher erweitert werden, erhöht sich die Speicherbelegung. Andererseits scheint SXSSF die Speicherbelegung zu verringern, indem die Anzahl der zugänglichen Leitungen verringert wird. (Bitte weisen Sie darauf hin, wenn ein Missverständnis vorliegt)

Nehmen Sie Korrekturen vor

Fix 1

Versuchen Sie, den vorherigen Code mithilfe der SXSSF-Arbeitsmappe zu ersetzen.

Ersetzen Sie die XSSF-Arbeitsmappe durch die SXSSF-Arbeitsmappe


	File file = new File(tempPath);	//Temporärer Dateipfad Vorab eine Datei erstellt Verwenden Sie diese als Vorlage
	SXSSFWorkbook workbook = (SXSSFWorkbook) WorkbookFactory.create(file);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	SXSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Referenzzeile: Kopieren Sie den Stil jeder Zelle
	int rowCnt = 1;
	for (1 erfasste Daten: list) {	//Es wird davon ausgegangen, dass die Liste im Voraus erhalten wurde und die Quelle nicht angegeben ist.
		sheet.createRow(sheet.getLastRowNum() + 1);
		SXSSFRow newRow = sheet.getRow(sheet.getLastRowNum());
		int cellCnt = 0;
		SXSSFCell originCell = null;
		SXSSFCell newCell = null;
		CellStyle style = workbook.createCellStyle();
		originCell = baseRow.getCell(cellCnt);
		newCell = newRow.createCell(cellCnt++);
		//Zellenstil kopieren
		style.cloneStyleFrom(originCell.getCellStyle());
		newCell.setCellStyle(style);
		//Kopie des Zelltyps
		newCell.setCellType(originCell.getCellType());
		newCell.setCellValue(1 erfasste Daten.Wert);
		//Wiederholen Sie die Einstellung für 14 Spalten
	}
	//Erstellungsdatum festlegen
	sheet.getRow(0).getCell(2).setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
	//Basislinie löschen
	sheet.shiftRows(baseRow.getRowNum() + 1, sheet.getLastRowNum(), -1);
	//Schreiben Sie in den Ausgabestream und erhalten Sie das Byte-Array
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	workbook.write(baos);
	excelData = baos.toByteArray();
	baos.close();
	workbook.close();
	//Temporäre Datei löschen
	file.delete();
	//Fügen Sie das Byte-Array in die Antwort ein und laden Sie es herunter

Dies führt zu einem Fehler. .. ..

Fehlerinhalt


java.lang.ClassCastException: org.apache.poi.xssf.usermodel.XSSFWorkbook cannot be cast to org.apache.poi.xssf.streaming.SXSSFWorkbook

WorkbookFactory.create (Datei) unterstützt SXSSF Workbook nicht. Nach der Untersuchung kann beim Erstellen einer SXSSF-Arbeitsmappeninstanz eine XSSF-Arbeitsmappeninstanz als Argument angegeben werden.

Fix 2

Instanziierung der SXSSF-Arbeitsmappe


	File file = new File(tempPath);	//Temporärer Dateipfad Vorab eine Datei erstellt Verwenden Sie diese als Vorlage
	XSSFWorkbook original = (XSSFWorkbook) WorkbookFactory.create(file);
	SXSSFWorkbook workbook = new SXSSFWorkbook(original);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	SXSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Referenzzeile: Kopieren Sie den Stil jeder Zelle
	original.close();	//Wird in die SXSSF-Arbeitsmappe geladen(?)Schließen für

Ersetzen Sie die Instanziierung durch die obige und führen Sie sie erneut aus. Wenn die obige SXSSFSheet.getLastRowNum () ausgeführt wird, wird sie nullpo.

Fehlerinhalt 2


java.lang.NullPointerException

Da die Vorlagendatei durch Instanziieren der XSSF-Arbeitsmappe gelesen wird, stellte ich mir vor, dass auf die letzte Zeile der vorhandenen Zeile der Vorlagendatei zugegriffen werden kann, aber ein Fehler aufgetreten ist. Geben Sie daher die letzte Anzahl der vorhandenen Zeilen "6" an und führen Sie sie erneut aus. Der folgende Fehler wird auftreten.

Fehlerinhalt 3


java.lang.IllegalArgumentException: Attempting to write a row[6] in the range [0,6] that is already written to disk.

Anscheinend sind die vorhandenen Zeilen der von XSSF Workbook gelesenen Datei zum Zeitpunkt der Instanziierung von SXSSF Workbook nicht zugänglich.

Fix 3

Aufgrund der diesmaligen Implementierungsspezifikationen möchten wir jedoch den Zugriff auf vorhandene Leitungen ermöglichen. Aufgrund verschiedener Versuche und Irrtümer wurde festgestellt, dass auf die vorhandene Leitung in der folgenden Form zugegriffen werden kann. (Ich habe die richtige Methode nicht bestätigt, aber es kann getan werden)

Greifen Sie auf eine vorhandene Zeile in der Vorlagendatei zu


	File file = new File(tempPath);	//Temporärer Dateipfad Vorab eine Datei erstellt Verwenden Sie diese als Vorlage
	XSSFWorkbook original = (XSSFWorkbook) WorkbookFactory.create(file);
	XSSFSheet orgSheet = original.getSheetAt(0);
	XSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Referenzzeile: Kopieren Sie den Stil jeder Zelle
	SXSSFWorkbook workbook = new SXSSFWorkbook(original);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	original.close();	//Wird in die SXSSF-Arbeitsmappe geladen(?)Schließen für

Bei der obigen Ausführung ist beim Aufrufen von SXSSFWorkbook.write () ein Fehler aufgetreten.

Fehlerinhalt 4


java.io.IOException: Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data. This may indicate that the file is used to inflate memory usage and thus could pose a security risk. You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit. Counter: 820224, cis.counter: 8192, ratio: 0.009987515605493134Limits: MIN_INFLATE_RATIO: 0.01

Nach der Untersuchung kann dies durch Aufrufen von ZipSecureFile.setMinInflateRatio () vermieden werden.

Abgeschlossene Version

Die endgültige fertige Quelle mit dem Aufruf von ZipSecureFile.setMinInflateRatio () wird unten angezeigt.

Quelle abgeschlossen


	File file = new File(tempPath);	//Temporärer Dateipfad Vorab eine Datei erstellt Verwenden Sie diese als Vorlage
	XSSFWorkbook original = (XSSFWorkbook) WorkbookFactory.create(file);
	XSSFSheet orgSheet = original.getSheetAt(0);
	SXSSFWorkbook workbook = new SXSSFWorkbook(original);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	Row baseRow = orgSheet.getRow(orgSheet.getLastRowNum());
	int rowCnt = 1;
	int rowNum = 6;
	boolean isFirst = true;
	for (1 erfasste Daten: list) {    //Es wird davon ausgegangen, dass die Liste im Voraus erhalten wurde und die Quelle nicht angegeben ist.
		SXSSFRow newRow = sheet.createRow(rowNum++);
		int cellCnt = 0;
		XSSFCell originCell = null;
		SXSSFCell newCell = null;
		CellStyle style = workbook.createCellStyle();
		if (isFirst) {    //Führen Sie nur die erste Zeile aus
			sheet.changeRowNum(newRow, 5);
			rowNum = 6;
			baseRow = newRow;
			isFirst = false;
		}
	}
	//Schreiben Sie in den Ausgabestream und erhalten Sie das Byte-Array
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	ZipSecureFile.setMinInflateRatio(0.001);	//Es scheint, dass sie die Komprimierungsrate und die Größe eines Eintrags überprüfen...
	workbook.write(baos);
	excelData = baos.toByteArray();
	baos.close();
	workbook.close();
	original.close();
	//Temporäre Datei löschen
	file.delete();
	//Fügen Sie das Byte-Array in die Antwort ein und laden Sie es herunter

Beim Ausführen tritt der OutOfMemory-Fehler nicht mehr auf.

Recommended Posts

Ich möchte OutOfMemory bei der Ausgabe großer Dateien mit POI vermeiden
Vermeiden Sie Zip-Bomb-Fehler beim Lesen großer Dateien mit POI
Konvertieren Sie große XLSX-Dateien mit Apache POI in CSV
[Rails] Ich möchte beim Übergang mit link_to Daten zu Params hinzufügen
Ich möchte DBViewer mit Eclipse 2018-12 verwenden! !!
Ich möchte Java8 für jeden mit Index verwenden
Ich möchte mit Firestore von Rails spielen
Ich möchte die Aggregationsverarbeitung mit Spring-Batch durchführen
[Rails] Ich möchte CSS mit Webpacker laden
Ich möchte eine von Git verwaltete Datei löschen
Ich möchte den Dunkelmodus mit der SWT-App verwenden
Ich möchte eine bestimmte Datei mit WatchService überwachen
Ich möchte Benutzer mit Devise + OmniAuth bei Rails authentifizieren
Ich möchte Bildschirmübergänge mit Kotlin und Java machen!
Ich möchte devise_parameter_sanitizer einzeln festlegen, wenn ich zwei Geräte erstelle
Ich möchte beim Schreiben von Testcode zufällig Informationen generieren
Ich möchte eine mit Rails 6 erstellte App an GitHub senden
Ich möchte eine Liste mit Kotlin und Java erstellen!
Ich möchte eine Funktion mit Kotlin und Java erstellen!
Wenn Sie explizit ODER oder UND mit Ransack schreiben möchten
Selbst in Java möchte ich true mit == 1 && a == 2 && a == 3 ausgeben
Ich möchte Dateien, die bei der Arbeit verwendet werden, einfach sichern
Ich möchte manuell eine Autorisierungs-E-Mail mit Devise senden
Ich möchte verschiedene Funktionen mit Kotlin und Java implementieren!
docker-compose.yml, wenn Sie MySQL auf Docker laufen lassen möchten
lombok.config, wenn Sie @Qualifier mit lombok an @RequiredArgsConstructor übergeben möchten
Ich möchte den Startbefehl mit Docker-Compose an Postgres übergeben.
[Java] Ich möchte Standardeingabe und Standardausgabe mit JUnit testen
Ich möchte Zeichen konvertieren ...
Ich möchte die Notwendigkeit des Testens beurteilen, indem ich die Unterschiede der Klassendateien beim Refactoring von Java vergleiche.
Ich möchte rekursiv nach Dateien in einem bestimmten Verzeichnis suchen
Ich möchte mit link_to [Hinweis] eine Schaltfläche mit einem Zeilenumbruch erstellen.
Ich möchte SONY Kopfhörer WH-1000XM4 mit LDAC mit Ubuntu 20.04 verbinden! !!
Ich möchte die Generierung / Öffnung einer Protokolldatei mit log4j # FileAppender verknüpfen
Wenn ich zu Heroku drücke, ärgere ich mich darüber, dass das Vorkompilieren von Assets fehlgeschlagen ist.
Ich möchte eine Browsing-Funktion mit Ruby on Rails hinzufügen
Ich möchte mit Kotlin und Java zum vorherigen Bildschirm zurückkehren!
Ich möchte Spring Local Time mit MySQL Time (auch Millisekunden) einfügen.
Beim Versuch, PowerMock mit JUnit zu verwenden, wurde ein IllegalAccessError angezeigt
Wovon ich süchtig war, als ich die Google-Authentifizierung mit Rails implementierte
[Java] Ich möchte mit dem Schlüssel im Objekt eindeutig arbeiten
Ich möchte eine asynchrone Verarbeitung und periodische Ausführung mit Rail durchführen !!!
Ich möchte mit einem regulären Ausdruck zwischen Zeichenketten extrahieren
Um Fehler beim Starten von miChecker zu vermeiden
Ich habe versucht, mit Java zu interagieren
Laden Sie große Dateien mit Apache JMeter herunter
Ich möchte Bilder mit REST Controller von Java und Spring anzeigen!
Probleme, denen ich beim Erstellen der Digdag-Umgebung mit Docker verfallen war
Ich möchte im Dialogfeld mehrere Elemente mit einem benutzerdefinierten Layout auswählen