【问题标题】:Read Rows Sequentially, but On Demand按顺序读取行,但按需读取
【发布时间】:2017-10-06 08:44:27
【问题描述】:

我需要逐行遍历 Excel 电子表格,但不是一次。我想保持一个流对电子表格打开,然后将该对象传递给另一个方法。此方法会不时要求下一行数据。这个方法已经成立了,不能改。

我最初尝试使用 XSSFWorkbook 执行此操作,效果非常好,直到我用完真实数据的内存。

然后我尝试切换到 SAX 解析器,使用 XSSFSheetXMLHandler,并使用类似于 Apache POI 项目提供的示例一 XLSX2CSV 的自定义解析器。但这会导致一次处理所有行;如果我将它们存储在内存中以供以后阅读,我也会用完内存。我也无法再访问DataFormatter,我需要它来处理单元格值。

有人可以给我指出一个允许我这样做的示例/类吗?

【问题讨论】:

  • 举个很好的例子,应该没问题。我最关心的是单元格格式。 DataFormatter 让它变得简单,但我自己在 XML 中......也需要一个很好的例子。
  • 好吧,为了格式化 XML,您可以通过 Java Transformer 类使用 XSLT:stackoverflow.com/questions/4604497/xslt-processing-with-java
  • 附注这个问题也对你有用:stackoverflow.com/questions/504689/…,SAX 和我上面提供的 XSLT 示例应该可以解决这个问题。 XSLT 相当简单,可以将其视为 XML 的样式表。
  • @MasterYoda 不幸的是,XML 选项实际上与我在问题中提到的相同。它使用自定义处理程序并一次处理所有行。我查看了您发送的 SAX 链接,我认为 SAX 是正确的选择。随着时间的推移,我不需要太多的上下文,一次只需要几行数据。到目前为止,我发现的所有 SAX 示例都使用处理程序并一次性解析整个工作表。

标签: java excel apache-poi


【解决方案1】:

我首选的 XML 流 API 是 StAX

知道*.xlsx 文件只是一个ZIP 存档和apache pois OPCPackage 因为这是一个ZipPackage,我们可以考虑以下方法:

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

以下示例创建了一个基本应用程序,该应用程序使用按钮单击逐行执行此操作。

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

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

接下来我们必须读取并解析包含共享字符串的/xl/sharedStrings.xml 包部分。我们还必须阅读和解析包含单元格样式的/xl/styles.xml 包部分。我们需要样式来检测数值是日期还是数字,如果是数字,那么是什么数字。这是必要的,因为Excel 将各种数字存储为双精度值。日期也是双精度数,表示 01/01/1900 之后的天数,小数部分为 1h = 1/24, 1m = 1/24/60, 1s = 1/24/60/60。

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

【讨论】:

  • 感谢您的回复,但您的解决方案比我希望承担的工作要多得多:) 我仍然想在这个领域找到一些东西,更类似于 XSSFSheetXMLHandler 提供的东西,但现在我已经重新设计了我的解决方案来解决这些限制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-15
  • 2014-02-12
  • 2020-08-04
  • 2019-10-05
  • 2018-04-04
  • 2017-01-03
相关资源
最近更新 更多