【问题标题】:Low memory writing/reading with Apache POI使用 Apache POI 进行低内存写入/读取
【发布时间】:2013-10-12 03:02:08
【问题描述】:

我正在尝试编写一个非常大的 XLSX 文件(4M+ 单元),但我遇到了一些内存问题。

我不能使用 SXSSF,因为我还需要读取模板中的现有单元格。

有什么办法可以减少内存占用吗?
也许结合流式阅读和流式写作?

【问题讨论】:

  • 您的文件有多少格式/花哨的东西?你有没有机会安排它,以便新行只需要进入新工作表?
  • 我们写的是新的单元格和行,但我们必须将公式标记为脏,否则 Excel 将显示其自己的值。也许值得考虑将其拆分为创建和更新,并将 SXSSF 仅用于创建。

标签: java apache-poi out-of-memory


【解决方案1】:

要处理低内存的大数据,最好的,我认为唯一的选择是 SXSSF api-s。 如果您需要读取现有单元格的一些数据,我假设您不需要同时使用整个 4M+。 在这种情况下,根据您的应用程序要求,您可以自己处理窗口大小,并将特定时间所需的数据量保留在内存中。 您可以先查看以下示例: http://poi.apache.org/spreadsheet/how-to.html#sxssf

有点像

SXSSFWorkbook wb = new SXSSFWorkbook(-1); // turn off auto-flushing and accumulate all rows in memory
// manually control how rows are flushed to disk 
if(rownum % NOR == 0) {
((SXSSFSheet)sh).flushRows(NOR); // retain NOR last rows and flush all others

希望这会有所帮助。

【讨论】:

  • SXSSF 的问题是你根本看不懂东西。您可以访问已编写的内容,但无法访问工作簿中已有的内容。
  • 对于阅读使用事件模型,poi.apache.org/spreadsheet/how-to.html 给出了一个很好的示例 - XSSF 和 SAX (Event API) 我自己使用过它,它有点复杂,但可以用于阅读大量数据不会耗尽内存。您也可以在poi.apache.org/spreadsheet/quick-guide.html 上查看小样本以了解想法。
  • 但问题是我们需要读取一个单元格,然后再次写入同一个单元格,这会很棘手。
【解决方案2】:

我使用 SAX 解析器来处理 XML 文档表示的事件。这是

import com.sun.org.apache.xerces.internal.parsers.SAXParser;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;


public class LowMemoryExcelFileReader {

    private String file;

    public LowMemoryExcelFileReader(String file) {
        this.file = file;
    }

    public List<String[]> read() {
        try {
            return processFirstSheet(file);
        } catch (Exception e) {
           throw new RuntimeException(e);
        }
    }

    private List<String []> readSheet(Sheet sheet) {
        List<String []> res = new LinkedList<>();
        Iterator<Row> rowIterator = sheet.rowIterator();

        while (rowIterator.hasNext()) {
            Row row = rowIterator.next();
            int cellsNumber = row.getLastCellNum();
            String [] cellsValues = new String[cellsNumber];

            Iterator<Cell> cellIterator = row.cellIterator();
            int cellIndex = 0;

            while (cellIterator.hasNext()) {
                Cell cell = cellIterator.next();
                cellsValues[cellIndex++] = cell.getStringCellValue();
            }

            res.add(cellsValues);
        }
        return res;
    }

    public String getFile() {
        return file;
    }

    public void setFile(String file) {
        this.file = file;
    }

    private List<String []> processFirstSheet(String filename) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
        XSSFReader r = new XSSFReader(pkg);
        SharedStringsTable sst = r.getSharedStringsTable();

        SheetHandler handler = new SheetHandler(sst);
        XMLReader parser = fetchSheetParser(handler);
        Iterator<InputStream> sheetIterator = r.getSheetsData();

        if (!sheetIterator.hasNext()) {
            return Collections.emptyList();
        }

        InputStream sheetInputStream = sheetIterator.next();
        BufferedInputStream bisSheet = new BufferedInputStream(sheetInputStream);
        InputSource sheetSource = new InputSource(bisSheet);
        parser.parse(sheetSource);
        List<String []> res = handler.getRowCache();
        bisSheet.close();
        return res;
    }

    public XMLReader fetchSheetParser(ContentHandler handler) throws SAXException {
        XMLReader parser = new SAXParser();
        parser.setContentHandler(handler);
        return parser;
    }

    /**
     * See org.xml.sax.helpers.DefaultHandler javadocs
     */
    private static class SheetHandler extends DefaultHandler {

        private static final String ROW_EVENT = "row";
        private static final String CELL_EVENT = "c";

        private SharedStringsTable sst;
        private String lastContents;
        private boolean nextIsString;

        private List<String> cellCache = new LinkedList<>();
        private List<String[]> rowCache = new LinkedList<>();

        private SheetHandler(SharedStringsTable sst) {
            this.sst = sst;
        }

        public void startElement(String uri, String localName, String name,
                                 Attributes attributes) throws SAXException {
            // c => cell
            if (CELL_EVENT.equals(name)) {
                String cellType = attributes.getValue("t");
                if(cellType != null && cellType.equals("s")) {
                    nextIsString = true;
                } else {
                    nextIsString = false;
                }
            } else if (ROW_EVENT.equals(name)) {
                if (!cellCache.isEmpty()) {
                    rowCache.add(cellCache.toArray(new String[cellCache.size()]));
                }
                cellCache.clear();
            }

            // Clear contents cache
            lastContents = "";
        }

        public void endElement(String uri, String localName, String name)
                throws SAXException {
            // Process the last contents as required.
            // Do now, as characters() may be called more than once
            if(nextIsString) {
                int idx = Integer.parseInt(lastContents);
                lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
                nextIsString = false;
            }

            // v => contents of a cell
            // Output after we've seen the string contents
            if(name.equals("v")) {
                cellCache.add(lastContents);
            }
        }

        public void characters(char[] ch, int start, int length)
                throws SAXException {
            lastContents += new String(ch, start, length);
        }

        public List<String[]> getRowCache() {
            return rowCache;
        }
    }
}

【讨论】:

  • 我试过你的代码,我碰巧注意到它没有读取最后一行,你知道吗?
  • 貌似处理空单元格,单元格必须有样式。如果您从 excel 检查生成的 xml 文件。如果它确实包含样式,它会出现在 xml 文件中。没有数据标签的空单元格(足以使用上面的代码)
  • 欢迎更新代码,用了很久。也许最新版本的库可以解决这个问题,或者提供解决这个问题的替代方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-18
  • 2014-01-01
  • 2017-03-25
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多