[JAVA] Je veux éviter OutOfMemory lors de la sortie de gros fichiers avec POI

Il existe un processus pour sortir un fichier Excel à l'aide de POI. C'est une spécification qui produit jusqu'à 10000 lignes de données dans 48 colonnes, mais lorsque j'ai essayé de sortir 10000 lignes, une erreur OutOfMemory s'est produite. Notez comment traiter de tels cas.

Ne dites pas que Java ne gère pas une si grande quantité de données dans Excel en premier lieu. ..

Implémentation du problème

L'image de la source où le problème est survenu est la suivante.

Source du problème


	File file = new File(tempPath);	//Chemin du fichier temporaire Création d'un fichier à l'avance Utilisez-le comme modèle
	XSSFWorkbook workbook = (XSSFWorkbook) WorkbookFactory.create(file);
	XSSFSheet sheet = workbook.getSheetAt(0);
	XSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Ligne de référence: copier le style de chaque cellule
	int rowCnt = 1;
	for (1 données acquises: list) {	//La liste n'est pas précisée car elle est obtenue à l'avance.
		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++);
		//Copier le style de cellule
		style.cloneStyleFrom(originCell.getCellStyle());
		newCell.setCellStyle(style);
		//Copie du type de cellule
		newCell.setCellType(originCell.getCellType());
		newCell.setCellValue(1 données acquises.valeur);
		//Répéter le réglage pour 14 colonnes
	}
	//Définir la date de création
	sheet.getRow(0).getCell(2).setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
	//Supprimer la ligne de base
	sheet.shiftRows(baseRow.getRowNum() + 1, sheet.getLastRowNum(), -1);
	//Écrire dans le flux de sortie et obtenir un tableau d'octets
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	workbook.write(baos);
	excelData = baos.toByteArray();
	baos.close();
	workbook.close();
	//Supprimer le fichier temporaire
	file.delete();
	//Mettez le tableau d'octets dans la réponse et téléchargez-le

Les grandes lignes du processus sont les suivantes.

Une erreur OutOfMemory s'est produite lors de l'appel de XSSFWorkbook.write ().

Contre-mesures

Lorsque j'ai recherché sur Google la méthode de contre-mesure, il est dit que le traitement sera plus léger si le classeur SXSSF est utilisé à la place du classeur XSSF. SXSSFWorkbook J'ai évoqué les documents suivants.

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

C'est une extension de XSSF. XSSF peut accéder à toutes les lignes et toutes les fonctions de l'API peuvent être utilisées, mais comme toutes les informations de ligne sont développées en mémoire, l'occupation de la mémoire augmente. D'autre part, SXSSF semble réduire l'occupation de la mémoire en réduisant le nombre de lignes accessibles. (Veuillez préciser s'il y a un malentendu)

Faire des corrections

Réparer 1

Essayez de remplacer le code précédent à l'aide du classeur SXSSF.

Remplacer le classeur XSSF par le classeur SXSSF


	File file = new File(tempPath);	//Chemin du fichier temporaire Création d'un fichier à l'avance Utilisez-le comme modèle
	SXSSFWorkbook workbook = (SXSSFWorkbook) WorkbookFactory.create(file);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	SXSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Ligne de référence: copier le style de chaque cellule
	int rowCnt = 1;
	for (1 données acquises: list) {	//La liste n'est pas précisée car elle est obtenue à l'avance.
		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++);
		//Copier le style de cellule
		style.cloneStyleFrom(originCell.getCellStyle());
		newCell.setCellStyle(style);
		//Copie du type de cellule
		newCell.setCellType(originCell.getCellType());
		newCell.setCellValue(1 données acquises.valeur);
		//Répéter le réglage pour 14 colonnes
	}
	//Définir la date de création
	sheet.getRow(0).getCell(2).setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
	//Supprimer la ligne de base
	sheet.shiftRows(baseRow.getRowNum() + 1, sheet.getLastRowNum(), -1);
	//Écrire dans le flux de sortie et obtenir un tableau d'octets
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	workbook.write(baos);
	excelData = baos.toByteArray();
	baos.close();
	workbook.close();
	//Supprimer le fichier temporaire
	file.delete();
	//Mettez le tableau d'octets dans la réponse et téléchargez-le

Cela entraînera une erreur. .. ..

contenu de l'erreur


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

WorkbookFactory.create (fichier); ne prend pas en charge le classeur SXSSF. Après examen, il est possible de spécifier une instance de classeur XSSF comme argument lors de la création d'une instance de classeur SXSSF.

Réparer 2

Instanciation du classeur SXSSF


	File file = new File(tempPath);	//Chemin du fichier temporaire Création d'un fichier à l'avance Utilisez-le comme modèle
	XSSFWorkbook original = (XSSFWorkbook) WorkbookFactory.create(file);
	SXSSFWorkbook workbook = new SXSSFWorkbook(original);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	SXSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Ligne de référence: copier le style de chaque cellule
	original.close();	//Chargé dans le classeur SXSSF(?)Fermer pour

Remplacez l'instanciation par ce qui précède et exécutez-la à nouveau. Lorsque le SXSSFSheet.getLastRowNum () ci-dessus est exécuté, il devient nullpo.

Contenu de l'erreur 2


java.lang.NullPointerException

Étant donné que le fichier modèle est lu en instanciant le classeur XSSF, j'ai imaginé que la dernière ligne de la ligne existante du fichier modèle était accessible, mais une erreur s'est produite. Par conséquent, spécifiez le dernier nombre de lignes existantes «6» et exécutez à nouveau. L'erreur suivante se produira.

Contenu de l'erreur 3


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

Apparemment, les lignes existantes du fichier lu par XSSF Workbook sont inaccessibles au moment de l'instanciation de SXSSF Workbook.

Réparer 3

Cependant, en raison des spécifications d'implémentation cette fois, nous souhaitons permettre l'accès aux lignes existantes. À la suite de divers essais et erreurs, il a été constaté que la ligne existante est accessible sous la forme suivante. (Je n'ai pas confirmé la bonne méthode, mais cela peut être fait)

Accéder à une ligne existante dans le fichier modèle


	File file = new File(tempPath);	//Chemin du fichier temporaire Création d'un fichier à l'avance Utilisez-le comme modèle
	XSSFWorkbook original = (XSSFWorkbook) WorkbookFactory.create(file);
	XSSFSheet orgSheet = original.getSheetAt(0);
	XSSFRow baseRow = sheet.getRow(sheet.getLastRowNum());	//Ligne de référence: copier le style de chaque cellule
	SXSSFWorkbook workbook = new SXSSFWorkbook(original);
	SXSSFSheet sheet = workbook.getSheetAt(0);
	original.close();	//Chargé dans le classeur SXSSF(?)Fermer pour

Dans l'exécution ci-dessus, une erreur s'est produite lors de l'appel de SXSSFWorkbook.write ().

Contenu de l'erreur 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

Après enquête, il est possible de l'éviter en appelant ZipSecureFile.setMinInflateRatio ().

Version terminée

La source de la version finale finale avec l'appel à ZipSecureFile.setMinInflateRatio () est indiquée ci-dessous.

Source terminée


	File file = new File(tempPath);	//Chemin du fichier temporaire Création d'un fichier à l'avance Utilisez-le comme modèle
	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 données acquises: list) {    //La liste n'est pas précisée car elle est obtenue à l'avance.
		SXSSFRow newRow = sheet.createRow(rowNum++);
		int cellCnt = 0;
		XSSFCell originCell = null;
		SXSSFCell newCell = null;
		CellStyle style = workbook.createCellStyle();
		if (isFirst) {    //Exécuter uniquement la première ligne
			sheet.changeRowNum(newRow, 5);
			rowNum = 6;
			baseRow = newRow;
			isFirst = false;
		}
	}
	//Écrire dans le flux de sortie et obtenir un tableau d'octets
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	ZipSecureFile.setMinInflateRatio(0.001);	//Il semble qu'ils vérifient le taux de compression et la taille d'une entrée...
	workbook.write(baos);
	excelData = baos.toByteArray();
	baos.close();
	workbook.close();
	original.close();
	//Supprimer le fichier temporaire
	file.delete();
	//Mettez le tableau d'octets dans la réponse et téléchargez-le

Lorsqu'elle est exécutée, l'erreur OutOfMemory ne se produit plus.

Recommended Posts

Je veux éviter OutOfMemory lors de la sortie de gros fichiers avec POI
Évitez les erreurs de bombe Zip lors de la lecture de gros fichiers avec POI
Convertissez de gros fichiers XLSX en CSV avec Apache POI
[Rails] Je souhaite ajouter des données aux paramètres lors de la transition avec link_to
Je souhaite utiliser DBViewer avec Eclipse 2018-12! !!
Je veux utiliser java8 forEach avec index
Je veux jouer avec Firestore de Rails
Je souhaite effectuer un traitement d'agrégation avec spring-batch
[Rails] Je veux charger du CSS avec webpacker
Je souhaite supprimer un fichier géré par Git
Je souhaite utiliser le mode sombre avec l'application SWT
Je souhaite surveiller un fichier spécifique avec WatchService
Je souhaite authentifier les utilisateurs auprès de Rails avec Devise + OmniAuth
Je veux faire des transitions d'écran avec kotlin et java!
Je veux définir devise_parameter_sanitizer individuellement lors de la création de deux appareils
Je souhaite générer des informations de manière aléatoire lors de l'écriture du code de test
Je veux pousser une application créée avec Rails 6 vers GitHub
Je veux faire une liste avec kotlin et java!
Je veux créer une fonction avec kotlin et java!
Lorsque vous souhaitez écrire explicitement OR ou AND avec ransack
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3
Je souhaite sauvegarder facilement les fichiers utilisés au travail
Je souhaite envoyer manuellement un e-mail d'autorisation avec Devise
Je veux implémenter diverses fonctions avec kotlin et java!
docker-compose.yml lorsque vous voulez garder mysql en cours d'exécution avec docker
lombok.config lorsque vous voulez passer @Qualifier à @RequiredArgsConstructor avec lombok
Je veux passer la commande de démarrage à postgres avec docker-compose.
[Java] Je souhaite tester l'entrée standard et la sortie standard avec JUnit
Je veux convertir des caractères ...
Je veux juger de la nécessité de tester en comparant les différences des fichiers de classe lors de la refactorisation de Java.
Je souhaite rechercher de manière récursive des fichiers dans un répertoire spécifique
Je veux créer un bouton avec un saut de ligne avec link_to [Note]
Je veux connecter un casque SONY WH-1000XM4 avec LDAC avec ubuntu 20.04! !!
Je veux accrocher la génération / ouverture d'un fichier journal avec log4j # FileAppender
Quand je pousse vers Heroku, je suis en colère contre l'échec de la précompilation des actifs.
Je souhaite ajouter une fonction de navigation avec ruby on rails
Je veux revenir à l'écran précédent avec kotlin et java!
Je veux INSÉRER l'heure locale du printemps avec l'heure MySQL (également en millisecondes)
J'ai eu un IllegalAccessError en essayant d'utiliser PowerMock avec JUnit
Ce à quoi j'étais accro lors de la mise en œuvre de l'authentification Google avec des rails
[Java] Je veux effectuer distinctement avec la clé dans l'objet
Je souhaite effectuer un traitement asynchrone et une exécution périodique avec Rail !!!
Je veux extraire entre des chaînes de caractères avec une expression régulière
Pour éviter les erreurs lors du démarrage de miChecker
J'ai essayé d'interagir avec Java
Téléchargez des fichiers volumineux avec Apache JMeter
Je veux afficher des images avec REST Controller de Java et Spring!
Problèmes auxquels j'étais accro lors de la création de l'environnement digdag avec docker
Rubocop se met en colère quand j'essaye de remplir zéro (remplir 0) avec Ruby
Je souhaite sélectionner plusieurs éléments avec une disposition personnalisée dans la boîte de dialogue