XLSX в CSV из-за ошибки памяти

Я нашел множество решений, как преобразовать файл XLSX в файл CSV с помощью Java, все решения используют: XSSFWorkbook. Проблема, с которой я сталкиваюсь, заключается в том, что, вероятно, в потоке слишком много данных. Я просто не понимаю, почему, файл всего 4 МБ.

КОД:

// For storing data into CSV files
    StringBuffer data = new StringBuffer();
    try {
        FileOutputStream fos = new FileOutputStream(outputFile);
        System.out.println("Getting input stream.");
        // Get the workbook object for XLS file
        XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(inputFile));
        System.out.println(" - Done");
        // Get first sheet from the workbook
        XSSFSheet sheet = workbook.getSheetAt(0);
        Cell cell;
        Row row;

        // Iterate through each rows from first sheet
        Iterator<Row> rowIterator = sheet.iterator();
        System.out.println(" - Reading xlsx rows.");
        while (rowIterator.hasNext()) {
            i++;

            row = rowIterator.next();
            // For each row, iterate through each columns
            Iterator<Cell> cellIterator = row.cellIterator();
            while (cellIterator.hasNext()) {
                cell = cellIterator.next();

                switch (cell.getCellType()) {
                case Cell.CELL_TYPE_BOOLEAN:
                    data.append(cell.getBooleanCellValue() + ";");
                    break;

                case Cell.CELL_TYPE_NUMERIC:
                    data.append(cell.getNumericCellValue() + ";");
                    break;

                case Cell.CELL_TYPE_STRING:
                    data.append(cell.getStringCellValue() + ";");
                    break;

                case Cell.CELL_TYPE_BLANK:
                    data.append("" + ";");
                    break;

                default:
                    data.append(cell + ";");
                }

            }
            data.append('\n');
            int limit = 10000;
            if ((i % limit) == 0) {
                System.out.println(" - Writing " + limit + " data.");
                fos.write(data.toString().getBytes());
                fos.flush();
                data = null;
                data = new StringBuffer();
                System.out.println(" - Data written.");
            }
        }

        fos.write(data.toString().getBytes());
        fos.flush();
        fos.close();

Ошибка указывает на строку в операторе switch, где я добавляю что-то к данным (StringBuffer), но я обнуляю его, поэтому это не должно быть проблемой.


person Ondrej Tokar    schedule 18.09.2014    source источник
comment
Вы запихиваете вещи в StringBuffer, которые могут быть не такими эффективными. Зачем вообще хранить это в памяти? Просто создайте одну строку, напишите это в файл и двигайтесь дальше (вы, вероятно, захотите использовать BufferedWriter. Также почему вы создаете строку и преобразуете ее в byte[], которая дублирует объем памяти. В зависимости от скорости вашей JVM и GC вы получаете много дополнительных объектов.Одна вещь, с которой нужно начать, это снизить лимит и вместо создания нового StringBuffer просто очистить его и использовать повторно.Вместо StringBUffer я предлагаю использовать StringBuilder.   -  person M. Deinum    schedule 18.09.2014
comment
Да, раньше я использовал только setLength(0), но проблема была не в этом, а во время fileInputStream.   -  person Ondrej Tokar    schedule 18.09.2014
comment
Большие объекты могут задерживаться, поэтому повторное использование может быть лучше. Тот факт, что это происходит во время чтения, не означает, что это происходит из-за чтения. Ваша память заполняется, и я бы первым делом починил большой StringBuffer. Еще одно замечание: вы используете StringBuffer, но по-прежнему используете String concat для добавления строк. Я предлагаю удалить + ";" из вашего заявления о случаях и добавить data.append(';') после утверждений о случаях. Сохраняет создание дополнительной строки для concat.   -  person M. Deinum    schedule 18.09.2014


Ответы (3)


Теперь вы, возможно, не сможете использовать SXSSFWorkbook (поскольку он предназначен только для записи), но вы сможете преобразовать свою программу в потоковый стиль, используя API на основе SAX. Редактировать: Еще одна вещь, которую вы можете попробовать, - это создать XSSFWorkbook из файла вместо InputStream (я помню, где-то читал, что для кода на основе файла требуется меньше памяти).

(Первая попытка: поскольку вы читаете данные последовательно, SXSSFWorkbook класс должен быть именно тем, что вам нужно.)

person llogiq    schedule 18.09.2014
comment
SXSSFWorkbook предназначен только для записи, проверьте: stackoverflow.com/questions/12513981/ - person Ondrej Tokar; 18.09.2014

Формат xlsx — это просто zip с содержимым xml и xml с общими строками. Следовательно, 4 МБ в сжатом виде вполне могут быть очень большими в несжатом виде.

Используя файловую систему zip, вы можете загружать общие строки в память, а затем последовательно читать содержимое xml, немедленно выводя.

Что касается двух внутренних файлов, вы можете использовать файловую систему zip java. Утомительно, но не сложно.

person Joop Eggen    schedule 18.09.2014

попробуйте этот код, он отлично работает для меня, я надеюсь, что он также работает для вас.

package com.converting;

import java.io.FileInputStream;
import java.io.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import com.opencsv.CSVWriter;
import java.util.Iterator;
import java.io.FileWriter;

public class XlsxtoCSV {

        public static void main(String[] args) throws Exception{
             FileInputStream input_document = new FileInputStream(new File("/home/blackpearl/Downloads/aa.xlsx"));
             XSSFWorkbook my_xls_workbook = new XSSFWorkbook(input_document); 
             XSSFSheet  my_worksheet = my_xls_workbook.getSheetAt(0); 

             Iterator<Row> rowIterator = my_worksheet.iterator();
             FileWriter my_csv=new FileWriter("/home/blackpearl/Downloads/Newaa.csv");
             CSVWriter my_csv_output=new CSVWriter(my_csv); 
             while(rowIterator.hasNext()) {
                     Row row = rowIterator.next(); 
                     int i=0;//String array
                     String[] csvdata = new String[20];
                     Iterator<Cell> cellIterator = row.cellIterator();
                             while(cellIterator.hasNext()) {
                                     Cell cell = cellIterator.next(); //Fetch CELL
                                     switch(cell.getCellType()) { //Identify CELL type
                                     case Cell.CELL_TYPE_STRING:
                                             csvdata[i]= cell.getStringCellValue();                                              
                                             break;
                                     }
                                     i=i+1;
                             }
             my_csv_output.writeNext(csvdata);
             }
             System.out.println("file imported");
             my_csv_output.close(); //close the CSV file
             input_document.close(); //close xlsx file
     }
}
person Sourabh Dubey    schedule 30.07.2018