2016-01-25 13 views
10

Tôi đã gặp một số vấn đề với việc phân tích cú pháp các tệp .xlsx với Apache POI - Tôi đang nhận được java.lang.OutOfMemoryError: Java heap space trong ứng dụng được triển khai của mình. Tôi chỉ xử lý các tệp dưới 5MB và khoảng 70.000 hàng nên tôi nghi ngờ đọc số câu hỏi khác là có điều gì đó không ổn.Apache POI nhanh hơn nhiều khi sử dụng HSSF so với XSSF - tiếp theo là gì?

Như được đề xuất trong this comment Tôi đã quyết định chạy SSPerformanceTest.java với các biến được đề xuất để xem có điều gì sai với mã hoặc thiết lập của tôi hay không. Kết quả cho thấy một sự khác biệt đáng kể giữa HSSF (.xls) và XSSF (.xlsx):

1) HSSF 50000 50 1: đã qua 1 giây

2) SXSSF 50000 50 1: đã qua 5 giây

3) XSSF 50000 50 1: đã qua 15 giây

Các FAQ đặc biệt nói:

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.

Tiếp theo, nó nói để chạy XLS2CSV.java mà tôi đã làm. Cho ăn trong tập tin XSSF được tạo ra ở trên (với 50000 hàng và 50 cột) mất khoảng 15 giây - cùng một số tiền cần để ghi tệp.

Có vấn đề gì với môi trường của tôi và nếu có thì làm cách nào để điều tra thêm?

Số liệu thống kê từ VisualVM hiển thị vùng heap được sử dụng quay lên tới 1,2Gb trong khi xử lý. Chắc chắn đây là cách quá cao xem xét đó là một buổi biểu diễn thêm trên đầu trang của đống so với trước khi chế biến bắt đầu?

Heap space is surely too high here?

Lưu ý: Các không gian đống ngoại lệ nêu trên chỉ xảy ra trong sản xuất (trên Google App Engine) và chỉ dành cho .xlsx file, tuy nhiên các cuộc thử nghiệm đề cập trong câu hỏi này đều được chạy trên máy phát triển của tôi với -Xmx2g . Tôi hy vọng rằng nếu tôi có thể khắc phục vấn đề về thiết lập phát triển của mình, nó sẽ sử dụng ít bộ nhớ hơn khi triển khai.

Stack trace từ động cơ ứng dụng:

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

Bạn không đơn độc: http://stackoverflow.com/questions/34246083/apache-poi-performance – raggi

+0

Chết tiệt, tôi đã đọc rất nhiều câu hỏi ở đây nhưng không tìm thấy câu hỏi đó! Cảm ơn rất nhiều. Dường như nó là một vấn đề với thư viện sau đó, nếu sự im lặng từ danh sách gửi thư là bất cứ điều gì để đi theo. Có thể bắt đầu làm việc trên một workaround. – slugmandrew

Trả lời

5

Tôi đang gặp phải vấn đề tương tự để đọc tệp .xlsx cồng kềnh bằng Apache POI và tôi tình cờ gặp

excel-streaming-reader-github

thư viện này phục vụ như là một wrapper xung quanh rằng luồng API trong khi vẫn giữ cú pháp của API POI chuẩn

thư viện này có thể giúp bạn đọc các file lớn.

+0

Cảm ơn, điều này trông giống như điều tôi cần! Đó là một sự xấu hổ những vấn đề này không được ghi nhận mặc dù. – slugmandrew

+1

Vì điều này có vẻ như là giải pháp tốt nhất cho một vấn đề phổ biến (mặc dù tôi có thể phải ngã ba nó để làm cho nó chơi với công cụ ứng dụng), bạn nhận được loot :) – slugmandrew

2

Bảng XLSX trung bình tôi làm việc khoảng 18-22 tờ 750 000 hàng với 13-20 cột. Điều này đang quay trong ứng dụng web mùa xuân với nhiều chức năng khác. Tôi đã cung cấp cho toàn bộ ứng dụng không nhiều bộ nhớ: -Xms1024m -Xmx4096m - và nó hoạt động tuyệt vời!

Trước hết mã bán phá giá: việc tải mỗi hàng dữ liệu vào bộ nhớ và bắt đầu đổ nó là sai. Trong trường hợp của tôi (báo cáo từ cơ sở dữ liệu PostgreSQL) tôi đã làm lại quy trình kết xuất dữ liệu để sử dụng RowCallbackHandler để ghi vào XLSX của mình, trong khi tôi đạt đến "giới hạn" của tôi là 750000 hàng, tôi tạo trang tính mới. Và sổ làm việc được tạo với cửa sổ hiển thị gồm 50 hàng. Bằng cách này tôi có thể đổ khối lượng lớn: kích thước của tập tin XLSX là khoảng 1230Mb.

Một số mã để viết tờ:

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

Sau khi làm lại mã của tôi về bán phá giá các dữ liệu để XLSX, tôi đến vấn đề này, mà nó đòi hỏi văn phòng tại 64 bit để mở chúng. Vì vậy, tôi cần phải chia bảng tính của tôi với nhiều trang tính thành các tệp XLSX riêng biệt với các trang tính đơn lẻ để làm cho chúng có thể đọc được trên máy trung bình. Và một lần nữa tôi đã sử dụng các cửa sổ hiển thị nhỏ và xử lý trực tiếp, và giữ cho toàn bộ ứng dụng hoạt động tốt mà không có bất kỳ điểm tham quan nào của OutOfMemory.

Một số mã để đọc và tấm chia:

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

Vì vậy, nó đọc và ghi dữ liệu. Tôi đoán trong trường hợp của bạn, bạn nên làm lại mã của bạn để mô hình tương tự: giữ trong bộ nhớ chỉ có dấu chân nhỏ của dữ liệu. Vì vậy, tôi sẽ đề nghị đọc tạo tùy chỉnh SheetContentsReader, sẽ đẩy dữ liệu vào một số cơ sở dữ liệu, nơi có thể dễ dàng xử lý, tổng hợp, v.v.

+0

Tôi rất đánh giá cao câu trả lời này và bạn chia sẻ mã của mình. Có vẻ như có nhiều giải pháp để khắc phục cùng một vấn đề! – slugmandrew

Các vấn đề liên quan