• 使用事件模式(Event API)读取Excel2007(.xlsx)文件


    POI的事件模式占用内存更小,它利用基础的XML数据进行处理,适用于愿意学习.xlsx文件结构以及在java中处理XML的开发人员;也能有效预防出现java.lang.OutOfMemoryError: GC overhead limit exceeded问题。

    1.了解下Excel文件的XML结构

    1.1、了解文件结构之前先来看一下准备的文件,这个文件只有一个sheet页,结构也很简单。

    markdown

    1.2、Excel2007是用XMl格式储存,将要读取的文件后缀名改为.zip或者直接用解压缩工具打开,就可以看到这个Excel文件的结构

    markdown

    1.3、[Content_Types].xml文件描述了整个Excel文件的结构,也将根据这个文件组织后面的读取工作

    markdown

    1.4、xl文件夹包括了需要的数据和格式信息,是重点关注的对象
    • workbook.xml: 记录了工作表基本信息,是我们重点关注的文件之一。
    • styles.xml: 记录了每个单元格的样式。
    • worksheets: 里面包括了我们的每个sheet的信息,储存在xml文件中。
      markdown
    1.5、workbook.xml重点关注的就是sheets和sheet两个标签
    • sheet标签中name属性记录的就是sheet的名称
    • sheet标签中r:id属性记录了当前sheet和之前提到的记录sheet信息的xml之间的对应关系,储存在_rels文件夹下的xml文件中。
    • sheet标签还有一个属性state标识来是否隐藏。
      重点备注信息:r:id="rId3"是获取数据关键
      markdown
    1.6、一般一个Excel文件有几个sheet页,就会有几个XML文件与之对应。其中sheet页和xml文件就是根据【新建 Microsoft Excel 工作表xl_relsworkbook.xml.rels】文件对应起来的

    重点备注信息:如下图所示,所有的信息都是在标签中,使用需要根据自己的当前sheel1中的格式获得数据
    markdown

    .读取.xlsx文件实例(java代码)

    import com.inspur.evaluation.message.consume.receive.utils.StringHelper;
    import org.apache.poi.openxml4j.opc.OPCPackage;
    import org.apache.poi.openxml4j.opc.PackageAccess;
    import org.apache.poi.util.IOUtils;
    import org.apache.poi.xssf.eventusermodel.XSSFReader;
    import org.apache.poi.xssf.model.SharedStringsTable;
    import org.apache.poi.xssf.usermodel.XSSFRichTextString;
    import org.xml.sax.Attributes;
    import org.xml.sax.InputSource;
    import org.xml.sax.SAXException;
    import org.xml.sax.XMLReader;
    import org.xml.sax.helpers.DefaultHandler;
    import org.xml.sax.helpers.XMLReaderFactory;
    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    public class POIEventModelUtil {
    
        public static void main(String[] args) throws Exception {
            OPCPackage pkg = OPCPackage.open("E:/ceshi/广州市评价详情明细数据20191105.xlsx", PackageAccess.READ);
            XSSFReader r = new XSSFReader(pkg);
    		//根据workbook.xml中r:id的值获得流
            InputStream is = r.getSheet("rId3");
            //debug 查看转换的xml原始文件,方便理解后面解析时的处理,
            byte[] isBytes = IOUtils.toByteArray(is);
            //读取流,查看文件内容
            streamOut(new ByteArrayInputStream(isBytes));
    
            //下面是SST 的索引会用到的
            SharedStringsTable sst = r.getSharedStringsTable();
            System.out.println("excel的共享字符表sst------------------start");
            sst.writeTo(System.out);
            System.out.println();
            System.out.println("excel的共享字符表sst------------------end");
    
            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
            List<List<String>> container = new ArrayList<>();
            parser.setContentHandler(new Myhandler(sst, container));
    
            InputSource inputSource = new InputSource(new ByteArrayInputStream(isBytes));
            parser.parse(inputSource);
    
            is.close();
    
            printContainer(container);
        }
    
        /**
         * 输出获得excel内容
         * @param container
         */
        public static void printContainer(List<List<String>> container) {
            System.out.println("excel内容------------- -start");
            for (List<String> stringList : container) {
                for (String str : stringList) {
                    System.out.printf("%3s", str + " | ");
                }
                System.out.println();
            }
            System.out.println("excel内容---------------end");
        }
    
        /**
         * 读取流,查看文件内容
         * @param in
         * @throws Exception
         */
        public static void streamOut(InputStream in) throws Exception {
            System.out.println("excel转为xml------------start");
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) != -1) {
                System.out.write(buf, 0, len);
            }
            System.out.println();
            System.out.println("excel转为xml------------end");
        }
    }
    
    class Myhandler extends DefaultHandler {
    
        //取SST 的索引对应的值
        private SharedStringsTable sst;
    
        public void setSst(SharedStringsTable sst) {
            this.sst = sst;
        }
    
        //解析结果保存
        private List<List<String>> container;
    
        public Myhandler(SharedStringsTable sst, List<List<String>> container) {
            this.sst = sst;
            this.container = container;
        }
    
        /**
         * 存储cell标签下v标签包裹的字符文本内容
         * 在v标签开始后,解析器自动调用characters()保存到 lastContents
         * 【但】当cell标签的属性 s是 t时, 表示取到的lastContents是 SharedStringsTable 的index值
         * 需要在v标签结束时根据 index(lastContents)获取一次真正的值
         */
        private String lastContents;
    
        //有效数据矩形区域,A1:Y2
        private String dimension;
    
        //根据dimension得出每行的数据长度
        private int longest;
    
        //上个有内容的单元格id,判断空单元格
        private String lastCellid;
    
        //上一行id, 判断空行
        private String lastRowid;
    
        // 判断单元格cell的c标签下是否有v,否则可能数据错位
        private boolean hasV = false;
    
    
        //行数据保存
        private List<String> currentRow;
    
        //单元格内容是SST 的索引
        private boolean isSSTIndex = false;
    
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
          //System.out.println("startElement:"+qName);
            lastContents = "";
            if (qName.equals("dimension")) {
                dimension = attributes.getValue("ref");
                longest = covertRowIdtoInt(dimension.substring(dimension.indexOf(":") + 1));
            }
            //行开始
            if (qName.equals("row")) {
                String rowNum = attributes.getValue("r");
                //判断空行
                if (lastRowid != null) {
                    //与上一行相差2, 说明中间有空行
                    int gap = Integer.parseInt(rowNum) - Integer.parseInt(lastRowid);
                    if (gap > 1) {
                        gap -= 1;
                        while (gap > 0) {
                            container.add(new ArrayList<>());
                            gap--;
                        }
                    }
                }
    
                lastRowid = attributes.getValue("r");
                currentRow = new ArrayList<>();
            }
            if (qName.equals("c")) {
                String rowId = attributes.getValue("r");
                //空单元判断,添加空字符到list
                if (lastCellid != null) {
                    int gap = covertRowIdtoInt(rowId) - covertRowIdtoInt(lastCellid);
                    for (int i = 0; i < gap - 1; i++) {
                        currentRow.add("");
                    }
                } else {
                    //第一个单元格可能不是在第一列
                    if (!"A1".equals(rowId)) {
                        for (int i = 0; i < covertRowIdtoInt(rowId) - 1; i++) {
                            currentRow.add("");
                        }
                    }
                }
                lastCellid = rowId;
    
                //判断单元格的值是SST的索引,不能直接characters方法取值
                if (attributes.getValue("t") != null && attributes.getValue("t").equals("s")) {
                    isSSTIndex = true;
                } else {
                    isSSTIndex = false;
                }
            }
        }
    
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
    
            //行结束,存储一行数据
            if (qName.equals("row")) {
                //判断最后一个单元格是否在最后,补齐列数
                //【注意】有的单元格只修改单元格格式,而没有内容,会出现c标签下没有v标签,导致currentRow少
                if (covertRowIdtoInt(lastCellid) < longest) {
                    int min = Math.min(currentRow.size(), covertRowIdtoInt(lastCellid));
                    for (int i = 0; i < longest - min; i++) {
                        currentRow.add("");
                    }
                }
                container.add(currentRow);
                lastCellid = null;
            }
    
            //单元格结束,没有v时需要补位
            if (qName.equals("c")) {
                if (!hasV) currentRow.add("");
                hasV = false;
            }
    
            //单元格内容标签结束,characters方法会被调用处理内容
            //2019-12-29 13:09:14  因为当前读取的sheel1.xml中内容存储大多都在<t></t>标签中,因此在此新增此单元格
            if (qName.equals("v") || qName.equals("t")) {
                hasV = true;
                //单元格的值是SST 的索引
                if (isSSTIndex) {
                    String sstIndex = lastContents.toString();
                    try {
                        int idx = Integer.parseInt(sstIndex);
                        XSSFRichTextString rtss = new XSSFRichTextString(
                                sst.getEntryAt(idx));
                        lastContents = rtss.toString();
                        if (StringHelper.isNotEmpty(lastContents)) {
                            currentRow.add(lastContents);
                        } else {
                            currentRow.add("");
                        }
                    } catch (NumberFormatException ex) {
                        System.out.println(lastContents);
                    }
                } else {
                    currentRow.add(lastContents);
                }
            }
        }
    
    
        /**
         * 获取element的文本数据
         *
         * @see org.xml.sax.ContentHandler#characters
         */
        public void characters(char[] ch, int start, int length) throws SAXException {
            lastContents += new String(ch, start, length);
        }
    
        /**
         *
         * @param cellId 单元格定位id,行列号
         * @return
         */
        public static int covertRowIdtoInt(String cellId) {
            StringBuilder sb = new StringBuilder();
            String column = "";
            //从cellId中提取列号
            for (char c : cellId.toCharArray()) {
                if (Character.isAlphabetic(c)) {
                    sb.append(c);
                } else {
                    column = sb.toString();
                }
            }
            //列号字符转数字
            int result = 0;
            for (char c : column.toCharArray()) {
                result = result * 26 + (c - 'A') + 1;
            }
            return result;
        }
    
        public static void main(String[] args) {
            System.out.println(Myhandler.covertRowIdtoInt("AB7"));
        }
    }
    ###本文出自以下文章,
    [POI事件模式指北(二)-Excel2007](https://yq.aliyun.com/articles/690513 "POI事件模式指北(二)-Excel2007")
  • 相关阅读:
    DataList控件部分使用方法
    评教系统——设计的重要性
    2010.7——2011.7年度总结
    Javascript初步
    asp.net天轰穿视频学习总结
    Datatable中对某列求和,三种不同情况下的方法
    DataTable删除多行
    C#语言的结构体布局
    geoserver、openlayers、PostgreSQL 开发环境配置
    字节流和结构体的转换[转]
  • 原文地址:https://www.cnblogs.com/xiaoBlog2016/p/12114832.html
Copyright © 2020-2023  润新知