按顺序读取行,但按需

我需要逐行遍历Excel电子表格,但不是一次全部遍历。 我想保持一个stream向电子表格打开,然后将该对象传递给另一个方法。 这个方法会不时要求下一行数据。 这种方法已经build立,所以我不能改变它。

我最初尝试使用XSSFWorkbook ,它工作得很好,直到我用完了真正的数据。

然后,我尝试使用XSSFSheetXMLHandler切换到SAXparsing器,并使用类似于Apache POI项目提供的示例一个XLSX2CSV的自定义parsing器。 但是这会导致一次所有的行处理; 如果我将它们存储在内存中以供稍后阅读,我也会耗尽内存。 我也没有访问DataFormatter ,我需要进行单元格值处理。

有人可以指我一个例子/类,让我这样做吗?

我喜欢的用于XML的Streaming API是StAX 。

知道一个*.xlsx文件只是一个ZIP压缩文件,而apache poi的OPCPackage是一个ZipPackage ,我们可以考虑以下方法:

  • *.xlsx Excel ZipPackage获取/xl/worksheets/sheetN.xml包部分。
  • 创build一个StAX阅读器。
  • 现在我们可以使用这个阅读器从这个XML读取。

下面的例子创build了一个基本的应用程序,它使用一个button单击来逐行进行。

 import java.awt.*; import java.awt.event.*; import javax.swing.*; import org.apache.poi.openxml4j.opc.*; import javax.xml.stream.*; import javax.xml.stream.events.*; import javax.xml.namespace.QName; import java.util.regex.Pattern; public class GetExcelRowByRow extends JPanel implements ActionListener { protected JButton button; protected JTextArea textArea; private final static String newline = "\n"; //file path to Excel file and sheet number to work with private final static String filepath = "file.xlsx"; private final static int scheetnr = 1; private StaxExcelRowByRowReader reader; public GetExcelRowByRow() { super(new GridBagLayout()); button = new JButton("Next Row"); button.addActionListener(this); textArea = new JTextArea(15, 50) { @Override public boolean getScrollableTracksViewportWidth() { return true; } }; textArea.setLineWrap(true); textArea.setEditable(false); JScrollPane scrollPane = new JScrollPane(textArea); GridBagConstraints c = new GridBagConstraints(); c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; add(button, c); c.fill = GridBagConstraints.BOTH; c.weightx = 1.0; c.weighty = 1.0; add(scrollPane, c); try { reader = new StaxExcelRowByRowReader(filepath, scheetnr); } catch (Exception ex) { ex.printStackTrace(); } } @Override public void actionPerformed(ActionEvent evt) { String row = "Row not found..."; try { row = reader.getNextRow(); } catch (Exception ex) { ex.printStackTrace(); } textArea.append(row + newline); textArea.setCaretPosition(textArea.getDocument().getLength()); } public StaxExcelRowByRowReader getReader() { return reader; } private static void createAndShowGUI() { JFrame frame = new JFrame("Get Excel row by row"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); GetExcelRowByRow app = new GetExcelRowByRow(); frame.add(app); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent windowEvent) { try { app.getReader().close(); } catch (Exception ex) { ex.printStackTrace(); } System.exit(0); } }); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } //class for reading a /xl/worksheets/sheetN.xml package part from a *.xlsx Excel ZipPackage private class StaxExcelRowByRowReader { private XMLEventReader sheetreader; private OPCPackage opcpackage; public StaxExcelRowByRowReader(String filepath, int sheetnr) { try { opcpackage = OPCPackage.open(filepath, PackageAccess.READ); //get the sheet package part PackagePart sheetpart = opcpackage.getPartsByName(Pattern.compile("/xl/worksheets/sheet"+sheetnr+".xml")).get(0); //create reader for the sheet package part sheetreader = XMLInputFactory.newInstance().createXMLEventReader(sheetpart.getInputStream()); } catch (Exception ex) { ex.printStackTrace(); } } //method for getting the next row from the reader public String getNextRow() throws Exception { StringBuffer row = new StringBuffer(); boolean valueFound = false; boolean nextValueIsSharedString = false; while(sheetreader.hasNext()){ XMLEvent event = sheetreader.nextEvent(); if(event.isStartElement()) { StartElement startElement = (StartElement)event; QName startElementName = startElement.getName(); if(startElementName.getLocalPart().equalsIgnoreCase("row")) { //start element of row row.append("<row"); row.append(" " + startElement.getAttributeByName(new QName("r"))); row.append(">"); } else if(startElementName.getLocalPart().equalsIgnoreCase("c")) { //start element of cell row.append("<c"); row.append(" " + startElement.getAttributeByName(new QName("r"))); row.append(" " + startElement.getAttributeByName(new QName("s"))); row.append(" " + startElement.getAttributeByName(new QName("t"))); row.append(">"); Attribute type = startElement.getAttributeByName(new QName("t")); if (type != null && "s".equals(type.getValue())) { nextValueIsSharedString = true; } else { nextValueIsSharedString = false; } } else if(startElementName.getLocalPart().equalsIgnoreCase("v")) { //start element of value row.append("<v>"); valueFound = true; } } else if(event.isCharacters() && valueFound) { Characters characters = (Characters)event; if (nextValueIsSharedString) { row.append("shared string: " + characters.getData()); } else { row.append(characters.getData()); } } else if(event.isEndElement()) { EndElement endElement = (EndElement)event; QName endElementName = endElement.getName(); if(endElementName.getLocalPart().equalsIgnoreCase("v")) { //end element of value row.append("</v>"); valueFound = false; } else if(endElementName.getLocalPart().equalsIgnoreCase("c")) { //end element of cell row.append("</c>"); } else if(endElementName.getLocalPart().equalsIgnoreCase("row")) { //end element of row row.append("</row>"); return row.toString(); } } } return "No more rows."; } public void close() throws Exception { if (sheetreader != null) sheetreader.close(); if (opcpackage != null) opcpackage.close(); } } } 

当然,这只是一个草案,以显示原则。 整个应用程序将更多的代码。

接下来,我们将不得不读取并parsing包含共享string的/xl/sharedStrings.xml包部分。 我们还必须读取和parsing包含单元格样式的/xl/styles.xml包部分。 我们需要样式来检测一个数字值是一个date还是一个数字,如果是一个数字,那么是一个什么样的数字。 这是必要的,因为Excel将所有types的数字存储为双精度值。 date也是数字双打,也就是01/01/1900之后的几天,小数部分为1h = 1 / 24,1m = 1/24 / 60,1s = 1/24/60/60。

但是可以使用与/xl/worksheets/sheetN.xml包部分相同的方法。