2016-01-25 3 views
10

У меня возникли проблемы с разбором файлов .xlsx с Apache POI - я получаю java.lang.OutOfMemoryError: Java heap space в моем развернутом приложении. Я обрабатываю файлы только под 5 МБ и около 70 000 строк, поэтому мое подозрение от чтения других вопросов - это то, что что-то не так.Apache POI гораздо быстрее использует HSSF, чем XSSF - что дальше?

Как было предложено в this comment Я решил запустить SSPerformanceTest.java с предлагаемыми переменными, поэтому, если что-то не так с моим кодом или настройкой. Результаты показывают значительную разницу между HSSF (.xls) и XSSF (.xlsx):

1) HSSF 50000 50 1: Прошедшее 1 секунды

2) SXSSF 50000 50 1: Прошедшее 5 секунд

3) XSSF 50000 50 1: Прошедшее 15 секунд

FAQ специфически говорит:

If you can't run that with 50,000 rows and 50 columns in all of HSSF, XSSF and SXSSF in under 3 seconds (ideally a lot less!), the problem is with your environment.

Далее говорится, что он выполнил XLS2CSV.java, который я сделал. Подача в файл XSSF, сгенерированный выше (с 50000 строк и 50 столбцов), занимает около 15 секунд - столько же, сколько требуется для записи файла.

Что-то не так с моей окружающей средой, и если да, то как я буду исследовать дальше?

Статистика от VisualVM показывает, что куча использовала съемку до 1,2 ГБ во время обработки. Конечно, это слишком высоко, учитывая, что это дополнительный концерт на вершине кучи по сравнению с началом обработки?

Heap space is surely too high here?

Примечание: куча пространства исключение упоминалось выше, происходит только в производстве (на Google App Engine) и только для .xlsx файлов, однако тесты, упомянутые в этом вопросе все были работать на моей машине развития с -Xmx2g , Я надеюсь, что если я смогу исправить проблему в моей настройке разработки, она будет использовать меньше памяти при развертывании.

Стек след от приложения двигателя:

Caused by: java.lang.OutOfMemoryError: Java heap space at org.apache.xmlbeans.impl.store.Cur.createElementXobj(Cur.java:260) at org.apache.xmlbeans.impl.store.Cur$CurLoadContext.startElement(Cur.java:2997) at org.apache.xmlbeans.impl.store.Locale$SaxHandler.startElement(Locale.java:3211) at org.apache.xmlbeans.impl.piccolo.xml.Piccolo.reportStartTag(Piccolo.java:1082) at org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseAttributesNS(PiccoloLexer.java:1802) at org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseOpenTagNS(PiccoloLexer.java:1521)

+0

Вы не одиноки: http://stackoverflow.com/questions/34246083/apache-poi-performance – raggi

+0

Черт, я так много читал здесь, но не нашел этого! Большое спасибо. Кажется, это проблема с библиотекой, если тишина из списка рассылки - это что-то, что нужно сделать. Могу начать работу над обходным решением. – slugmandrew

ответ

5

Я столкнулся с такой же проблемой, чтобы читать громоздкий файл .xlsx с использованием Apache POI и я наткнулся на

excel-streaming-reader-github

Эта библиотека служит оберткой вокруг этого потокового API, сохраняя при этом синтаксис стандартного POI API

Эта библиотека может помочь вам читать большие файлы.

+0

Спасибо, это похоже на то, что мне нужно! Это позор, эти проблемы не документированы. – slugmandrew

+1

Как кажется, это лучшее решение общей проблемы (хотя мне, возможно, придется разветвить ее, чтобы она играла с движком приложения), вы получаете добычу :) – slugmandrew

2

В среднем XLSX лист Работает составляет около 18-22 листов 750 000 строк с 13-20 колоннами. Это вращение в веб-приложении Spring с множеством других функций. Я дал всему приложению не столько большую память: -Xms1024m -Xmx4096m - и он отлично работает!

Прежде всего, код сброса: неправильно загружать каждую строку данных в память и запускать ее сбрасывать. В моем случае (отчетность из базы данных PostgreSQL) я переработал процедуру дампа данных, чтобы использовать RowCallbackHandler для записи в мой XLSX, во время этого, когда я достигаю «моего предела» из 750000 строк, я создаю новый лист. И рабочая книга создается с окном видимости в 50 строк. Таким образом, я могу сбрасывать огромные объемы: размер файла XLSX составляет около 1230 МБ.

Некоторый код писать листы:

jdbcTemplate.query(
     new PreparedStatementCreator() { 
      @Override 
      public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { 
       PreparedStatement statement = connection.prepareStatement(finalQuery, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 
       statement.setFetchSize(100); 
       statement.setFetchDirection(ResultSet.FETCH_FORWARD); 
       return statement; 
      } 
     }, new RowCallbackHandler() { 
      Sheet sheet = null; 
      int i = 750000; 
      int tableId = 0; 

      @Override 
      public void processRow(ResultSet resultSet) throws SQLException { 
       if (i == 750000) { 
        tableId++; 
        i = 0; 
        sheet = wb.createSheet(sheetName.concat(String.format("%02d%n", tableId))); 


        Row r = sheet.createRow(0); 

        Cell c = r.createCell(0); 
        c.setCellValue("id"); 
        c = r.createCell(1); 
        c.setCellValue("Дата"); 
        c = r.createCell(2); 
        c.setCellValue("Комментарий"); 
        c = r.createCell(3); 
        c.setCellValue("Сумма операции"); 
        c = r.createCell(4); 
        c.setCellValue("Дебет"); 
        c = r.createCell(5); 
        c.setCellValue("Страхователь"); 
        c = r.createCell(6); 
        c.setCellValue("Серия договора"); 
        c = r.createCell(7); 
        c.setCellValue("Номер договора"); 
        c = r.createCell(8); 
        c.setCellValue("Основной агент"); 
        c = r.createCell(9); 
        c.setCellValue("Кредит"); 
        c = r.createCell(10); 
        c.setCellValue("Программа"); 
        c = r.createCell(11); 
        c.setCellValue("Дата начала покрытия"); 
        c = r.createCell(12); 
        c.setCellValue("Дата планового окончания покрытия"); 
        c = r.createCell(13); 
        c.setCellValue("Периодичность уплаты взносов"); 
       } 
       i++; 

       PremiumEntity e = PremiumEntity.builder() 
        .Id(resultSet.getString("id")) 
        .OperationDate(resultSet.getDate("operation_date")) 
        .Comments(resultSet.getString("comments")) 
        .SumOperation(resultSet.getBigDecimal("sum_operation").doubleValue()) 
        .DebetAccount(resultSet.getString("debet_account")) 
        .Strahovatelname(resultSet.getString("strahovatelname")) 
        .Seria(resultSet.getString("seria")) 
        .NomPolica(resultSet.getLong("nom_polica")) 
        .Agentname(resultSet.getString("agentname")) 
        .CreditAccount(resultSet.getString("credit_account")) 
        .Program(resultSet.getString("program")) 
        .PoliciStartDate(resultSet.getDate("polici_start_date")) 
        .PoliciPlanEndDate(resultSet.getDate("polici_plan_end_date")) 
        .Periodichn(resultSet.getString("id_periodichn")) 
        .build(); 

       Row r = sheet.createRow(i); 
       Cell c = r.createCell(0); 
       c.setCellValue(e.getId()); 

       if (e.getOperationDate() != null) { 
        c = r.createCell(1); 
        c.setCellStyle(dateStyle); 
        c.setCellValue(e.getOperationDate()); 
       } 

       c = r.createCell(2); 
       c.setCellValue(e.getComments()); 

       c = r.createCell(3); 
       c.setCellValue(e.getSumOperation()); 

       c = r.createCell(4); 
       c.setCellValue(e.getDebetAccount()); 

       c = r.createCell(5); 
       c.setCellValue(e.getStrahovatelname()); 

       c = r.createCell(6); 
       c.setCellValue(e.getSeria()); 

       c = r.createCell(7); 
       c.setCellValue(e.getNomPolica()); 

       c = r.createCell(8); 
       c.setCellValue(e.getAgentname()); 

       c = r.createCell(9); 
       c.setCellValue(e.getCreditAccount()); 

       c = r.createCell(10); 
       c.setCellValue(e.getProgram()); 

       if (e.getPoliciStartDate() != null) { 
        c = r.createCell(11); 
        c.setCellStyle(dateStyle); 
        c.setCellValue(e.getPoliciStartDate()); 
       } 
       ; 

       if (e.getPoliciPlanEndDate() != null) { 
        c = r.createCell(12); 
        c.setCellStyle(dateStyle); 
        c.setCellValue(e.getPoliciPlanEndDate()); 
       } 

       c = r.createCell(13); 
       c.setCellValue(e.getPeriodichn()); 
      } 
     }); 

После переделки мой код на сброс данных в XLSX, я пришел к проблеме, что она требует офис в 64 бита, чтобы открыть их. Поэтому мне нужно разделить мою книгу с большим количеством листов на отдельные файлы XLSX с отдельными листами, чтобы сделать их доступными для чтения на среднем компьютере. И снова я использовал небольшие окна видимости и поточную обработку и поддерживал работоспособность всего приложения без каких-либо взглядов OutOfMemory.

Некоторый код для чтения и разделения листов:

 OPCPackage opcPackage = OPCPackage.open(originalFile, PackageAccess.READ); 


     ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(opcPackage); 
     XSSFReader xssfReader = new XSSFReader(opcPackage); 
     StylesTable styles = xssfReader.getStylesTable(); 
     XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData(); 
     int index = 0; 
     while (iter.hasNext()) { 
      InputStream stream = iter.next(); 
      String sheetName = iter.getSheetName(); 

      DataFormatter formatter = new DataFormatter(); 
      InputSource sheetSource = new InputSource(stream); 

      SheetToWorkbookSaver saver = new SheetToWorkbookSaver(sheetName); 
      try { 
       XMLReader sheetParser = SAXHelper.newXMLReader(); 
       ContentHandler handler = new XSSFSheetXMLHandler(
        styles, null, strings, saver, formatter, false); 
       sheetParser.setContentHandler(handler); 
       sheetParser.parse(sheetSource); 
      } catch(ParserConfigurationException e) { 
       throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage()); 
      } 

      stream.close(); 

      // this creates new File descriptors inside storage 
      FileDto partFile = new FileDto("report_".concat(StringUtils.trimToEmpty(sheetName)).concat(".xlsx")); 
      File cloneFile = fileStorage.read(partFile); 
      FileOutputStream cloneFos = new FileOutputStream(cloneFile); 
      saver.getWb().write(cloneFos); 
      cloneFos.close(); 
     } 

и

public class SheetToWorkbookSaver implements XSSFSheetXMLHandler.SheetContentsHandler { 

    private SXSSFWorkbook wb; 
    private Sheet sheet; 
    private CellStyle dateStyle ; 


    private Row currentRow; 

    public SheetToWorkbookSaver(String workbookName) { 
     this.wb = new SXSSFWorkbook(50); 
     this.dateStyle = this.wb.createCellStyle(); 
     this.dateStyle.setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat("dd.mm.yyyy")); 

     this.sheet = this.wb.createSheet(workbookName); 

    } 

    @Override 
    public void startRow(int rowNum) { 
     this.currentRow = this.sheet.createRow(rowNum); 
    } 

    @Override 
    public void endRow(int rowNum) { 

    } 

    @Override 
    public void cell(String cellReference, String formattedValue, XSSFComment comment) { 
     int thisCol = (new CellReference(cellReference)).getCol(); 
     Cell c = this.currentRow.createCell(thisCol); 
     c.setCellValue(formattedValue); 
     c.setCellComment(comment); 
    } 

    @Override 
    public void headerFooter(String text, boolean isHeader, String tagName) { 

    } 


    public SXSSFWorkbook getWb() { 
     return wb; 
    } 
} 

Так он считывает и записывает данные. Думаю, в вашем случае вы должны переделать свой код на те же шаблоны: сохранить в памяти только малую площадь данных. Поэтому я бы предложил для чтения создать пользовательский SheetContentsReader, который будет толкать данные в какую-то базу данных, где ее можно легко обрабатывать, агрегировать и т. Д.

+0

Я очень ценю этот ответ, и вы делитесь своим кодом. Кажется, есть много решений для решения одной и той же проблемы! – slugmandrew

Смежные вопросы